diff --git a/Sources/SkipBridge/Tuples.swift b/Sources/SkipBridge/Tuples.swift new file mode 100644 index 0000000..b4c804f --- /dev/null +++ b/Sources/SkipBridge/Tuples.swift @@ -0,0 +1,172 @@ +// Copyright 2024 Skip +// +// This is free software: you can redistribute and/or modify it +// under the terms of the GNU Lesser General Public License 3.0 +// as published by the Free Software Foundation https://fsf.org + +/// Tuple conversions. +public final class SwiftTuple { + public static func javaObject(for tuple: (T0, T1)?, options: JConvertibleOptions) -> JavaObjectPointer? { + guard let tuple else { + return nil + } + let t0_java = (tuple.0 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t1_java = (tuple.1 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + if options.contains(.kotlincompat) { + return try! Java_Pair.create(ctor: Java_Pair_constructor_methodID, args: [t0_java, t1_java]) + } else { + return try! Java_Tuple2.create(ctor: Java_Tuple2_constructor_methodID, args: [t0_java, t1_java]) + } + } + + public static func tuple(forJavaObject tuple: JavaObjectPointer?, options: JConvertibleOptions) -> (T0, T1)? { + guard let tuple else { + return nil + } + let t0MethodID: JavaMethodID + let t1MethodID: JavaMethodID + if options.contains(.kotlincompat) { + t0MethodID = Java_Pair_first_methodID + t1MethodID = Java_Pair_second_methodID + } else { + t0MethodID = Java_Tuple2_e0_methodID + t1MethodID = Java_Tuple2_e1_methodID + } + let t0_java = try! JavaObjectPointer?.call(t0MethodID, on: tuple, options: options, args: []) + let t0_swift = (T0.self as! JConvertible.Type).fromJavaObject(t0_java, options: options) + let t1_java = try! JavaObjectPointer?.call(t1MethodID, on: tuple, options: options, args: []) + let t1_swift = (T1.self as! JConvertible.Type).fromJavaObject(t1_java, options: options) + return (t0_swift as! T0, t1_swift as! T1) + } + + public static func javaObject(for tuple: (T0, T1, T2)?, options: JConvertibleOptions) -> JavaObjectPointer? { + guard let tuple else { + return nil + } + let t0_java = (tuple.0 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t1_java = (tuple.1 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t2_java = (tuple.2 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + if options.contains(.kotlincompat) { + return try! Java_Triple.create(ctor: Java_Triple_constructor_methodID, args: [t0_java, t1_java, t2_java]) + } else { + return try! Java_Tuple3.create(ctor: Java_Tuple3_constructor_methodID, args: [t0_java, t1_java, t2_java]) + } + } + + public static func tuple(forJavaObject tuple: JavaObjectPointer?, options: JConvertibleOptions) -> (T0, T1, T2)? { + guard let tuple else { + return nil + } + let t0MethodID: JavaMethodID + let t1MethodID: JavaMethodID + let t2MethodID: JavaMethodID + if options.contains(.kotlincompat) { + t0MethodID = Java_Triple_first_methodID + t1MethodID = Java_Triple_second_methodID + t2MethodID = Java_Triple_third_methodID + } else { + t0MethodID = Java_Tuple3_e0_methodID + t1MethodID = Java_Tuple3_e1_methodID + t2MethodID = Java_Tuple3_e2_methodID + } + let t0_java = try! JavaObjectPointer?.call(t0MethodID, on: tuple, options: options, args: []) + let t0_swift = (T0.self as! JConvertible.Type).fromJavaObject(t0_java, options: options) + let t1_java = try! JavaObjectPointer?.call(t1MethodID, on: tuple, options: options, args: []) + let t1_swift = (T1.self as! JConvertible.Type).fromJavaObject(t1_java, options: options) + let t2_java = try! JavaObjectPointer?.call(t2MethodID, on: tuple, options: options, args: []) + let t2_swift = (T2.self as! JConvertible.Type).fromJavaObject(t2_java, options: options) + return (t0_swift as! T0, t1_swift as! T1, t2_swift as! T2) + } + + public static func javaObject(for tuple: (T0, T1, T2, T3)?, options: JConvertibleOptions) -> JavaObjectPointer? { + guard let tuple else { + return nil + } + let t0_java = (tuple.0 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t1_java = (tuple.1 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t2_java = (tuple.2 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t3_java = (tuple.3 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + return try! Java_Tuple4.create(ctor: Java_Tuple4_constructor_methodID, args: [t0_java, t1_java, t2_java, t3_java]) + } + + public static func tuple(forJavaObject tuple: JavaObjectPointer?, options: JConvertibleOptions) -> (T0, T1, T2, T3)? { + guard let tuple else { + return nil + } + let t0_java = try! JavaObjectPointer?.call(Java_Tuple4_e0_methodID, on: tuple, options: options, args: []) + let t0_swift = (T0.self as! JConvertible.Type).fromJavaObject(t0_java, options: options) + let t1_java = try! JavaObjectPointer?.call(Java_Tuple4_e1_methodID, on: tuple, options: options, args: []) + let t1_swift = (T1.self as! JConvertible.Type).fromJavaObject(t1_java, options: options) + let t2_java = try! JavaObjectPointer?.call(Java_Tuple4_e2_methodID, on: tuple, options: options, args: []) + let t2_swift = (T2.self as! JConvertible.Type).fromJavaObject(t2_java, options: options) + let t3_java = try! JavaObjectPointer?.call(Java_Tuple4_e3_methodID, on: tuple, options: options, args: []) + let t3_swift = (T3.self as! JConvertible.Type).fromJavaObject(t3_java, options: options) + return (t0_swift as! T0, t1_swift as! T1, t2_swift as! T2, t3_swift as! T3) + } + + public static func javaObject(for tuple: (T0, T1, T2, T3, T4)?, options: JConvertibleOptions) -> JavaObjectPointer? { + guard let tuple else { + return nil + } + let t0_java = (tuple.0 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t1_java = (tuple.1 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t2_java = (tuple.2 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t3_java = (tuple.3 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + let t4_java = (tuple.4 as! JConvertible).toJavaObject(options: options).toJavaParameter(options: options) + return try! Java_Tuple4.create(ctor: Java_Tuple5_constructor_methodID, args: [t0_java, t1_java, t2_java, t3_java, t4_java]) + } + + public static func tuple(forJavaObject tuple: JavaObjectPointer?, options: JConvertibleOptions) -> (T0, T1, T2, T3, T4)? { + guard let tuple else { + return nil + } + let t0_java = try! JavaObjectPointer?.call(Java_Tuple5_e0_methodID, on: tuple, options: options, args: []) + let t0_swift = (T0.self as! JConvertible.Type).fromJavaObject(t0_java, options: options) + let t1_java = try! JavaObjectPointer?.call(Java_Tuple5_e1_methodID, on: tuple, options: options, args: []) + let t1_swift = (T1.self as! JConvertible.Type).fromJavaObject(t1_java, options: options) + let t2_java = try! JavaObjectPointer?.call(Java_Tuple5_e2_methodID, on: tuple, options: options, args: []) + let t2_swift = (T2.self as! JConvertible.Type).fromJavaObject(t2_java, options: options) + let t3_java = try! JavaObjectPointer?.call(Java_Tuple5_e3_methodID, on: tuple, options: options, args: []) + let t3_swift = (T3.self as! JConvertible.Type).fromJavaObject(t3_java, options: options) + let t4_java = try! JavaObjectPointer?.call(Java_Tuple5_e4_methodID, on: tuple, options: options, args: []) + let t4_swift = (T4.self as! JConvertible.Type).fromJavaObject(t4_java, options: options) + return (t0_swift as! T0, t1_swift as! T1, t2_swift as! T2, t3_swift as! T3, t4_swift as! T4) + } +} + +private let Java_Tuple2 = try! JClass(name: "skip/lib/Tuple2") +private let Java_Tuple2_constructor_methodID = Java_Tuple2.getMethodID(name: "", sig: "(Ljava/lang/Object;Ljava/lang/Object;)V")! +private let Java_Tuple2_e0_methodID = Java_Tuple2.getMethodID(name: "get_e0", sig: "()Ljava/lang/Object;")! +private let Java_Tuple2_e1_methodID = Java_Tuple2.getMethodID(name: "get_e1", sig: "()Ljava/lang/Object;")! + +private let Java_Pair = try! JClass(name: "kotlin/Pair") +private let Java_Pair_constructor_methodID = Java_Pair.getMethodID(name: "", sig: "(Ljava/lang/Object;Ljava/lang/Object;)V")! +private let Java_Pair_first_methodID = Java_Pair.getMethodID(name: "getFirst", sig: "()Ljava/lang/Object;")! +private let Java_Pair_second_methodID = Java_Pair.getMethodID(name: "getSecond", sig: "()Ljava/lang/Object;")! + +private let Java_Tuple3 = try! JClass(name: "skip/lib/Tuple3") +private let Java_Tuple3_constructor_methodID = Java_Tuple3.getMethodID(name: "", sig: "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V")! +private let Java_Tuple3_e0_methodID = Java_Tuple3.getMethodID(name: "get_e0", sig: "()Ljava/lang/Object;")! +private let Java_Tuple3_e1_methodID = Java_Tuple3.getMethodID(name: "get_e1", sig: "()Ljava/lang/Object;")! +private let Java_Tuple3_e2_methodID = Java_Tuple3.getMethodID(name: "get_e2", sig: "()Ljava/lang/Object;")! + +private let Java_Triple = try! JClass(name: "kotlin/Triple") +private let Java_Triple_constructor_methodID = Java_Triple.getMethodID(name: "", sig: "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V")! +private let Java_Triple_first_methodID = Java_Triple.getMethodID(name: "getFirst", sig: "()Ljava/lang/Object;")! +private let Java_Triple_second_methodID = Java_Triple.getMethodID(name: "getSecond", sig: "()Ljava/lang/Object;")! +private let Java_Triple_third_methodID = Java_Triple.getMethodID(name: "getThird", sig: "()Ljava/lang/Object;")! + +private let Java_Tuple4 = try! JClass(name: "skip/lib/Tuple4") +private let Java_Tuple4_constructor_methodID = Java_Tuple4.getMethodID(name: "", sig: "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V")! +private let Java_Tuple4_e0_methodID = Java_Tuple4.getMethodID(name: "get_e0", sig: "()Ljava/lang/Object;")! +private let Java_Tuple4_e1_methodID = Java_Tuple4.getMethodID(name: "get_e1", sig: "()Ljava/lang/Object;")! +private let Java_Tuple4_e2_methodID = Java_Tuple4.getMethodID(name: "get_e2", sig: "()Ljava/lang/Object;")! +private let Java_Tuple4_e3_methodID = Java_Tuple4.getMethodID(name: "get_e3", sig: "()Ljava/lang/Object;")! + +private let Java_Tuple5 = try! JClass(name: "skip/lib/Tuple5") +private let Java_Tuple5_constructor_methodID = Java_Tuple5.getMethodID(name: "", sig: "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V")! +private let Java_Tuple5_e0_methodID = Java_Tuple5.getMethodID(name: "get_e0", sig: "()Ljava/lang/Object;")! +private let Java_Tuple5_e1_methodID = Java_Tuple5.getMethodID(name: "get_e1", sig: "()Ljava/lang/Object;")! +private let Java_Tuple5_e2_methodID = Java_Tuple5.getMethodID(name: "get_e2", sig: "()Ljava/lang/Object;")! +private let Java_Tuple5_e3_methodID = Java_Tuple5.getMethodID(name: "get_e3", sig: "()Ljava/lang/Object;")! +private let Java_Tuple5_e4_methodID = Java_Tuple5.getMethodID(name: "get_e4", sig: "()Ljava/lang/Object;")! diff --git a/Sources/SkipBridgeToKotlinCompatSamples/Samples.swift b/Sources/SkipBridgeToKotlinCompatSamples/Samples.swift index cd99b0a..f8fe4f5 100644 --- a/Sources/SkipBridgeToKotlinCompatSamples/Samples.swift +++ b/Sources/SkipBridgeToKotlinCompatSamples/Samples.swift @@ -9,6 +9,7 @@ import Foundation public class Compat { public let id: UUID public var urls: [URL] = [] + public var attempts: (Int, URL)? public init(id: UUID) { self.id = id diff --git a/Sources/SkipBridgeToKotlinSamples/Samples.swift b/Sources/SkipBridgeToKotlinSamples/Samples.swift index 9b665a7..69aaebd 100644 --- a/Sources/SkipBridgeToKotlinSamples/Samples.swift +++ b/Sources/SkipBridgeToKotlinSamples/Samples.swift @@ -209,6 +209,7 @@ public var swiftClosure1OptionalsVar: (String?) -> Int? = { s in s?.count } public var swiftIntArrayVar = [1, 2, 3] public var swiftStringSetVar: Set = ["a", "b", "c"] public var swiftIntStringDictionaryVar = [1: "a", 2: "b", 3: "c"] +public var swiftIntStringTuple = (1, "s") // MARK: Functions diff --git a/Sources/SkipBridgeToSwiftSamples/Samples.swift b/Sources/SkipBridgeToSwiftSamples/Samples.swift index 3051438..8438c46 100644 --- a/Sources/SkipBridgeToSwiftSamples/Samples.swift +++ b/Sources/SkipBridgeToSwiftSamples/Samples.swift @@ -211,6 +211,7 @@ public var kotlinClosure1OptionalsVar: (String?) -> Int? = { s in s?.count } public var kotlinIntArrayVar = [1, 2, 3] public var kotlinStringSetVar: Set = ["a", "b", "c"] public var kotlinIntStringDictionaryVar = [1: "a", 2: "b", 3: "c"] +public var kotlinIntStringTupleVar = (1, "s") // MARK: Functions diff --git a/Sources/SkipBridgeToSwiftSamplesTestsSupport/TestsSupport.swift b/Sources/SkipBridgeToSwiftSamplesTestsSupport/TestsSupport.swift index 07c3dff..a20976d 100644 --- a/Sources/SkipBridgeToSwiftSamplesTestsSupport/TestsSupport.swift +++ b/Sources/SkipBridgeToSwiftSamplesTestsSupport/TestsSupport.swift @@ -484,6 +484,10 @@ public func testSupport_kotlinClosure1OptionalsVar(value: String?) -> Int? { return i1 == i2 ? i1 : (i1 ?? 0) * 10000 + (i2 ?? 0) } +public func testSupport_kotlinClosure1Parameter(value: (Int) -> String) { + // We're only testing that using a closure as a parameter compiles cleanly +} + public func testSupport_kotlinIntArrayVar(value: [Int]) -> [Int] { kotlinIntArrayVar = value return kotlinIntArrayVar @@ -499,6 +503,11 @@ public func testSupport_kotlinIntStringDictionaryVar(value: [Int: String]) -> [I return kotlinIntStringDictionaryVar } +public func testSupport_kotlinIntStringTupleVar(value: (Int, String)) -> (Int, String) { + kotlinIntStringTupleVar = value + return kotlinIntStringTupleVar +} + public func testSupport_callKotlinThrowingFunction(shouldThrow: Bool) throws -> Int { return try kotlinThrowingFunction(shouldThrow: shouldThrow) } diff --git a/Tests/SkipBridgeToKotlinCompatSamplesTests/BridgeToKotlinCompatSamplesTests.swift b/Tests/SkipBridgeToKotlinCompatSamplesTests/BridgeToKotlinCompatSamplesTests.swift index 5bc8143..cadba77 100644 --- a/Tests/SkipBridgeToKotlinCompatSamplesTests/BridgeToKotlinCompatSamplesTests.swift +++ b/Tests/SkipBridgeToKotlinCompatSamplesTests/BridgeToKotlinCompatSamplesTests.swift @@ -32,4 +32,17 @@ final class BridgeToKotlinCompatTests: XCTestCase { XCTAssertEqual(list2[0], url) #endif } + + func testCompatTuplePair() { + #if SKIP + let uuid = java.util.UUID.randomUUID() + let compat = Compat(id: uuid) + XCTAssertEqual(uuid, compat.id) + + let url = java.net.URI("https://skip.tools") + compat.attempts = Pair(2, url) + XCTAssertEqual(2, compat.attempts?.first) + XCTAssertEqual(url, compat.attempts?.second) + #endif + } } diff --git a/Tests/SkipBridgeToKotlinSamplesTests/BridgeToKotlinSamplesTests.swift b/Tests/SkipBridgeToKotlinSamplesTests/BridgeToKotlinSamplesTests.swift index 9b937ab..4edbd19 100644 --- a/Tests/SkipBridgeToKotlinSamplesTests/BridgeToKotlinSamplesTests.swift +++ b/Tests/SkipBridgeToKotlinSamplesTests/BridgeToKotlinSamplesTests.swift @@ -395,6 +395,14 @@ final class BridgeToKotlinTests: XCTestCase { XCTAssertEqual(swiftIntStringDictionaryVar, Dictionary()) } + func testTuples() { + XCTAssertEqual(swiftIntStringTuple.0, 1) + XCTAssertEqual(swiftIntStringTuple.1, "s") + swiftIntStringTuple = (2, "a") + XCTAssertEqual(swiftIntStringTuple.0, 2) + XCTAssertEqual(swiftIntStringTuple.1, "a") + } + func testThrowsFunctions() throws { do { try swiftThrowingVoidFunction(shouldThrow: true) diff --git a/Tests/SkipBridgeToSwiftSamplesTestsSupportTests/BridgeToSwiftSamplesTests.swift b/Tests/SkipBridgeToSwiftSamplesTestsSupportTests/BridgeToSwiftSamplesTests.swift index 21163df..f548372 100644 --- a/Tests/SkipBridgeToSwiftSamplesTestsSupportTests/BridgeToSwiftSamplesTests.swift +++ b/Tests/SkipBridgeToSwiftSamplesTestsSupportTests/BridgeToSwiftSamplesTests.swift @@ -218,6 +218,12 @@ final class BridgeToSwiftTests: XCTestCase { XCTAssertEqual(roundtripped[5], "e") } + func testTuples() { + let roundtripped = testSupport_kotlinIntStringTupleVar(value: (2, "a")) + XCTAssertEqual(roundtripped.0, 2) + XCTAssertEqual(roundtripped.1, "a") + } + func testThrowingFunctions() throws { do { try testSupport_callKotlinThrowingVoidFunction(shouldThrow: true)