Skip to content

Commit c6184a3

Browse files
authored
jextract: add array and throwing functions support to java callbacks (#481)
1 parent bf5ca01 commit c6184a3

File tree

4 files changed

+198
-12
lines changed

4 files changed

+198
-12
lines changed

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public protocol CallbackProtocol {
2828
func withObject(_ input: MySwiftClass) -> MySwiftClass
2929
func withOptionalInt64(_ input: Int64?) -> Int64?
3030
func withOptionalObject(_ input: MySwiftClass?) -> Optional<MySwiftClass>
31+
func withInt64Array(_ input: [Int64]) -> [Int64]
32+
func withStringArray(_ input: [String]) -> [String]
33+
func withObjectArray(_ input: [MySwiftClass]) -> [MySwiftClass]
34+
func successfulThrowingFunction() throws
35+
func throwingFunction() throws
3136
}
3237

3338
public struct CallbackOutput {
@@ -43,6 +48,21 @@ public struct CallbackOutput {
4348
public let object: MySwiftClass
4449
public let optionalInt64: Int64?
4550
public let optionalObject: MySwiftClass?
51+
public let int64Array: [Int64]
52+
public let stringArray: [String]
53+
public let objectArray: [MySwiftClass]
54+
}
55+
56+
public func callProtocolVoid(_ callbacks: some CallbackProtocol) {
57+
callbacks.withVoid();
58+
}
59+
60+
public func callProtocolWithFailedThrowingFunction(_ callbacks: some CallbackProtocol) throws {
61+
try callbacks.throwingFunction();
62+
}
63+
64+
public func callProtocolWithSuccessfulThrowingFunction(_ callbacks: some CallbackProtocol) throws {
65+
try callbacks.successfulThrowingFunction();
4666
}
4767

4868
public func outputCallbacks(
@@ -58,7 +78,10 @@ public func outputCallbacks(
5878
string: String,
5979
object: MySwiftClass,
6080
optionalInt64: Int64?,
61-
optionalObject: MySwiftClass?
81+
optionalObject: MySwiftClass?,
82+
int64Array: [Int64],
83+
stringArray: [String],
84+
objectArray: [MySwiftClass]
6285
) -> CallbackOutput {
6386
return CallbackOutput(
6487
bool: callbacks.withBool(bool),
@@ -72,6 +95,9 @@ public func outputCallbacks(
7295
string: callbacks.withString(string),
7396
object: callbacks.withObject(object),
7497
optionalInt64: callbacks.withOptionalInt64(optionalInt64),
75-
optionalObject: callbacks.withOptionalObject(optionalObject)
98+
optionalObject: callbacks.withOptionalObject(optionalObject),
99+
int64Array: callbacks.withInt64Array(int64Array),
100+
stringArray: callbacks.withStringArray(stringArray),
101+
objectArray: callbacks.withObjectArray(objectArray)
76102
)
77103
}

Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,50 @@ public OptionalLong withOptionalInt64(OptionalLong input) {
8787
public Optional<MySwiftClass> withOptionalObject(Optional<MySwiftClass> input, SwiftArena swiftArena$) {
8888
return input;
8989
}
90+
91+
@Override
92+
public long[] withInt64Array(long[] input) {
93+
return input;
94+
}
95+
96+
@Override
97+
public String[] withStringArray(String[] input) {
98+
return input;
99+
}
100+
101+
@Override
102+
public MySwiftClass[] withObjectArray(MySwiftClass[] input, SwiftArena swiftArena$) {
103+
return input;
104+
}
105+
106+
@Override
107+
public void throwingFunction() throws Exception {
108+
throw new Exception("Failed in Java");
109+
}
110+
111+
@Override
112+
public void successfulThrowingFunction() throws Exception {
113+
114+
}
115+
}
116+
117+
@Test
118+
void voidTest() {
119+
JavaCallbacks callbacks = new JavaCallbacks();
120+
MySwiftLibrary.callProtocolVoid(callbacks);
121+
}
122+
123+
@Test
124+
void throwingFunction_thatDoesNotThrow() {
125+
JavaCallbacks callbacks = new JavaCallbacks();
126+
assertDoesNotThrow(() -> MySwiftLibrary.callProtocolWithSuccessfulThrowingFunction(callbacks));
127+
}
128+
129+
@Test
130+
void throwingFunction_thatThrows() {
131+
JavaCallbacks callbacks = new JavaCallbacks();
132+
Exception exception = assertThrows(Exception.class, () -> MySwiftLibrary.callProtocolWithFailedThrowingFunction(callbacks));
133+
assertEquals("Failed in Java", exception.getMessage());
90134
}
91135

92136
@Test
@@ -95,7 +139,28 @@ void primitiveCallbacks() {
95139
JavaCallbacks callbacks = new JavaCallbacks();
96140
var object = MySwiftClass.init(5, 3, arena);
97141
var optionalObject = Optional.of(MySwiftClass.init(10, 10, arena));
98-
var output = MySwiftLibrary.outputCallbacks(callbacks, true, (byte) 1, (char) 16, (short) 16, (int) 32, 64L, 1.34f, 1.34, "Hello from Java!", object, OptionalLong.empty(), optionalObject, arena);
142+
var int64Array = new long[]{1, 2, 3};
143+
var stringArray = new String[]{"Hey", "there"};
144+
var objectArray = new MySwiftClass[]{MySwiftClass.init(1, 1, arena), MySwiftClass.init(2, 2, arena)};
145+
var output = MySwiftLibrary.outputCallbacks(
146+
callbacks,
147+
true,
148+
(byte) 1,
149+
(char) 16,
150+
(short) 16,
151+
(int) 32,
152+
64L,
153+
1.34f,
154+
1.34,
155+
"Hello from Java!",
156+
object,
157+
OptionalLong.empty(),
158+
optionalObject,
159+
int64Array,
160+
stringArray,
161+
objectArray,
162+
arena
163+
);
99164

100165
assertEquals(1, output.getInt8());
101166
assertEquals(16, output.getUint16());
@@ -112,6 +177,14 @@ void primitiveCallbacks() {
112177
var optionalObjectOutput = output.getOptionalObject(arena);
113178
assertTrue(optionalObjectOutput.isPresent());
114179
assertEquals(10, optionalObjectOutput.get().getX());
180+
181+
assertArrayEquals(new long[]{1, 2,3}, output.getInt64Array());
182+
assertArrayEquals(new String[]{"Hey", "there"}, output.getStringArray());
183+
184+
var objectArrayOutput = output.getObjectArray(arena);
185+
assertEquals(2, objectArrayOutput.length);
186+
assertEquals(1, objectArrayOutput[0].getX());
187+
assertEquals(2, objectArrayOutput[1].getX());
115188
}
116189
}
117190
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ extension JNISwift2JavaGenerator {
133133

134134
private func translateParameter(parameterName: String, type: SwiftType) throws -> UpcallConversionStep {
135135

136+
if type.isDirectlyTranslatedToWrapJava {
137+
return .placeholder
138+
}
139+
136140
switch type {
137141
case .nominal(let nominalType):
138142
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
@@ -146,12 +150,17 @@ extension JNISwift2JavaGenerator {
146150
wrappedType: genericArgs[0]
147151
)
148152

149-
default:
150-
guard knownType.isDirectlyTranslatedToWrapJava else {
153+
case .array:
154+
guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else {
151155
throw JavaTranslationError.unsupportedSwiftType(type)
152156
}
157+
return try translateArrayParameter(
158+
name: parameterName,
159+
elementType: genericArgs[0]
160+
)
153161

154-
return .placeholder
162+
default:
163+
throw JavaTranslationError.unsupportedSwiftType(type)
155164
}
156165
}
157166

@@ -171,11 +180,32 @@ extension JNISwift2JavaGenerator {
171180
wrappedType: wrappedType
172181
)
173182

174-
case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite, .array:
183+
case .array(let elementType):
184+
return try translateArrayParameter(name: parameterName, elementType: elementType)
185+
186+
case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite:
175187
throw JavaTranslationError.unsupportedSwiftType(type)
176188
}
177189
}
178190

191+
private func translateArrayParameter(name: String, elementType: SwiftType) throws -> UpcallConversionStep {
192+
switch elementType {
193+
case .nominal(let nominalType):
194+
// We assume this is a JExtracted type
195+
return .map(
196+
.placeholder,
197+
body: .toJavaWrapper(
198+
.placeholder,
199+
name: "arrayElement",
200+
nominalType: nominalType
201+
)
202+
)
203+
204+
case .array, .composite, .existential, .function, .genericParameter, .metatype, .opaque, .optional, .tuple:
205+
throw JavaTranslationError.unsupportedSwiftType(.array(elementType))
206+
}
207+
}
208+
179209
private func translateOptionalParameter(name: String, wrappedType: SwiftType) throws -> UpcallConversionStep {
180210
let wrappedConversion = try translateParameter(parameterName: name, type: wrappedType)
181211
return .toJavaOptional(.map(.placeholder, body: wrappedConversion))
@@ -190,6 +220,10 @@ extension JNISwift2JavaGenerator {
190220
methodName: String,
191221
allowNilForObjects: Bool = false
192222
) throws -> UpcallConversionStep {
223+
if type.isDirectlyTranslatedToWrapJava {
224+
return .placeholder
225+
}
226+
193227
switch type {
194228
case .nominal(let nominalType):
195229
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
@@ -203,11 +237,14 @@ extension JNISwift2JavaGenerator {
203237
methodName: methodName
204238
)
205239

206-
default:
207-
guard knownType.isDirectlyTranslatedToWrapJava else {
240+
case .array:
241+
guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else {
208242
throw JavaTranslationError.unsupportedSwiftType(type)
209243
}
210-
return .placeholder
244+
return try self.translateArrayResult(elementType: genericArgs[0])
245+
246+
default:
247+
throw JavaTranslationError.unsupportedSwiftType(type)
211248
}
212249
}
213250

@@ -228,11 +265,32 @@ extension JNISwift2JavaGenerator {
228265
case .optional(let wrappedType):
229266
return try self.translateOptionalResult(wrappedType: wrappedType, methodName: methodName)
230267

231-
case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite, .array:
268+
case .array(let elementType):
269+
return try self.translateArrayResult(elementType: elementType)
270+
271+
case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite:
232272
throw JavaTranslationError.unsupportedSwiftType(type)
233273
}
234274
}
235275

276+
private func translateArrayResult(elementType: SwiftType) throws -> UpcallConversionStep {
277+
switch elementType {
278+
case .nominal(let nominalType):
279+
// We assume this is a JExtracted type
280+
return .map(
281+
.placeholder,
282+
body: .toSwiftClass(
283+
.unwrapOptional(.placeholder, message: "Element of array was nil"),
284+
name: "arrayElement",
285+
nominalType: nominalType
286+
)
287+
)
288+
289+
case .array, .composite, .existential, .function, .genericParameter, .metatype, .opaque, .optional, .tuple:
290+
throw JavaTranslationError.unsupportedSwiftType(.array(elementType))
291+
}
292+
}
293+
236294
private func translateOptionalResult(wrappedType: SwiftType, methodName: String) throws -> UpcallConversionStep {
237295
// The `fromJavaOptional` will handle the nullability
238296
let wrappedConversion = try translateResult(
@@ -340,3 +398,31 @@ extension JNISwift2JavaGenerator {
340398
}
341399
}
342400
}
401+
402+
extension SwiftType {
403+
/// Indicates whether this type is translated by `wrap-java`
404+
/// into the same type as `jextract`.
405+
///
406+
/// This means we do not have to perform any mapping when passing
407+
/// this type between jextract and wrap-java
408+
var isDirectlyTranslatedToWrapJava: Bool {
409+
switch self {
410+
case .nominal(let swiftNominalType):
411+
guard let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind else {
412+
return false
413+
}
414+
switch knownType {
415+
case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, .void:
416+
return true
417+
default:
418+
return false
419+
}
420+
421+
case .array(let elementType):
422+
return elementType.isDirectlyTranslatedToWrapJava
423+
424+
case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite:
425+
return false
426+
}
427+
}
428+
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ extension JNISwift2JavaGenerator {
152152
conversion.render(&printer, param.parameterName!)
153153
}
154154

155-
let javaUpcall = "\(wrapper.javaInterfaceVariableName).\(function.swiftFunctionName)(\(upcallArguments.joined(separator: ", ")))"
155+
let tryClause = function.originalFunctionSignature.isThrowing ? "try " : ""
156+
let javaUpcall = "\(tryClause)\(wrapper.javaInterfaceVariableName).\(function.swiftFunctionName)(\(upcallArguments.joined(separator: ", ")))"
156157

157158
let resultType = function.originalFunctionSignature.result.type
158159
let result = function.resultConversion.render(&printer, javaUpcall)

0 commit comments

Comments
 (0)