Skip to content

Commit a25d216

Browse files
authored
[JExtract/JNI] Add support for classes/structs as parameters and return values (#326)
1 parent 86da9ee commit a25d216

File tree

12 files changed

+883
-417
lines changed

12 files changed

+883
-417
lines changed

Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
public class MySwiftClass {
16-
let x: Int64
17-
let y: Int64
16+
public let x: Int64
17+
public let y: Int64
1818

1919
public let byte: UInt8 = 0
2020
public let constant: Int64 = 100
@@ -76,4 +76,12 @@ public class MySwiftClass {
7676
public func throwingFunction() throws {
7777
throw MySwiftClassError.swiftError
7878
}
79+
80+
public func sumX(with other: MySwiftClass) -> Int64 {
81+
return self.x + other.x
82+
}
83+
84+
public func copy() -> MySwiftClass {
85+
return MySwiftClass(x: self.x, y: self.y)
86+
}
7987
}

Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,25 @@ void isWarm() {
118118
assertFalse(c.isWarm());
119119
}
120120
}
121+
122+
@Test
123+
void sumWithX() {
124+
try (var arena = new ConfinedSwiftMemorySession()) {
125+
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
126+
MySwiftClass c2 = MySwiftClass.init(50, 10, arena);
127+
assertEquals(70, c1.sumX(c2));
128+
}
129+
}
130+
131+
@Test
132+
void copy() {
133+
try (var arena = new ConfinedSwiftMemorySession()) {
134+
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
135+
MySwiftClass c2 = c1.copy(arena);
136+
137+
assertEquals(20, c2.getX());
138+
assertEquals(10, c2.getY());
139+
assertNotEquals(c1.$memoryAddress(), c2.$memoryAddress());
140+
}
141+
}
121142
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 57 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ extension JNISwift2JavaGenerator {
117117
printer.println()
118118

119119
for initializer in decl.initializers {
120-
printInitializerBindings(&printer, initializer, type: decl)
120+
printFunctionBinding(&printer, initializer)
121121
printer.println()
122122
}
123123

@@ -176,75 +176,82 @@ extension JNISwift2JavaGenerator {
176176
}
177177

178178
private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
179-
guard let _ = translatedDecl(for: decl) else {
179+
guard let translatedDecl = translatedDecl(for: decl) else {
180180
// Failed to translate. Skip.
181181
return
182182
}
183183

184+
var modifiers = ["public"]
184185
if decl.isStatic || decl.isInitializer || !decl.hasParent {
185-
printStaticFunctionBinding(&printer, decl)
186-
} else {
187-
printMemberMethodBindings(&printer, decl)
186+
modifiers.append("static")
188187
}
189-
}
190188

191-
private func printStaticFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
192-
printDeclDocumentation(&printer, decl)
193-
printer.print(
194-
"public static native \(renderFunctionSignature(decl));"
195-
)
196-
}
189+
let translatedSignature = translatedDecl.translatedFunctionSignature
190+
let resultType = translatedSignature.resultType.javaType
191+
var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.asParameter)
192+
if translatedSignature.requiresSwiftArena {
193+
parameters.append("SwiftArena swiftArena$")
194+
}
195+
let throwsClause = decl.isThrowing ? " throws Exception" : ""
197196

198-
/// Renders Java bindings for member methods
199-
///
200-
/// Member methods are generated as a function that extracts the `selfPointer`
201-
/// and passes it down to another native function along with the arguments
202-
/// to call the Swift implementation.
203-
private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
204-
let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl.
197+
let modifiersStr = modifiers.joined(separator: " ")
198+
let parametersStr = parameters.joined(separator: ", ")
205199

206200
printDeclDocumentation(&printer, decl)
207-
printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in
208-
var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name)
209-
210-
let selfVarName = "self$"
211-
arguments.append(selfVarName)
201+
printer.printBraceBlock(
202+
"\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)"
203+
) { printer in
204+
printDowncall(&printer, decl)
205+
}
212206

213-
let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return "
207+
printNativeFunction(&printer, decl)
208+
}
214209

215-
printer.print(
216-
"""
217-
long \(selfVarName) = this.$memoryAddress();
218-
\(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", ")));
219-
"""
220-
)
210+
private func printNativeFunction(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
211+
let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl
212+
let nativeSignature = translatedDecl.nativeFunctionSignature
213+
let resultType = nativeSignature.result.javaType
214+
var parameters = nativeSignature.parameters
215+
if let selfParameter = nativeSignature.selfParameter {
216+
parameters.append(selfParameter)
221217
}
218+
let renderedParameters = parameters.map { "\($0.javaParameter.type) \($0.javaParameter.name)"}.joined(separator: ", ")
222219

223-
let returnType = translatedDecl.translatedFunctionSignature.resultType
224-
var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
225-
parameters.append("long selfPointer")
226-
printer.print("private static native \(returnType) $\(translatedDecl.name)(\(parameters.joined(separator: ", ")));")
220+
printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));")
227221
}
228222

229-
private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) {
230-
guard let translatedDecl = translatedDecl(for: decl) else {
231-
// Failed to translate. Skip.
232-
return
223+
private func printDowncall(
224+
_ printer: inout CodePrinter,
225+
_ decl: ImportedFunc
226+
) {
227+
let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl.
228+
let translatedFunctionSignature = translatedDecl.translatedFunctionSignature
229+
230+
// Regular parameters.
231+
var arguments = [String]()
232+
for parameter in translatedFunctionSignature.parameters {
233+
let lowered = parameter.conversion.render(&printer, parameter.parameter.name)
234+
arguments.append(lowered)
233235
}
234236

235-
printDeclDocumentation(&printer, decl)
236-
printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in
237-
let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name)
238-
printer.print(
239-
"""
240-
long self$ = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", ")));
241-
return new \(type.qualifiedName)(self$, swiftArena$);
242-
"""
243-
)
237+
// 'self' parameter.
238+
if let selfParameter = translatedFunctionSignature.selfParameter {
239+
let lowered = selfParameter.conversion.render(&printer, "this")
240+
arguments.append(lowered)
244241
}
245242

246-
let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
247-
printer.print("private static native long allocatingInit(\(parameters.joined(separator: ", ")));")
243+
//=== Part 3: Downcall.
244+
// TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names
245+
// using the registry?
246+
let downcall = "\(translatedDecl.parentName).\(translatedDecl.nativeFunctionName)(\(arguments.joined(separator: ", ")))"
247+
248+
//=== Part 4: Convert the return value.
249+
if translatedFunctionSignature.resultType.javaType.isVoid {
250+
printer.print("\(downcall);")
251+
} else {
252+
let result = translatedFunctionSignature.resultType.conversion.render(&printer, downcall)
253+
printer.print("return \(result);")
254+
}
248255
}
249256

250257
private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
@@ -288,24 +295,4 @@ extension JNISwift2JavaGenerator {
288295
)
289296
}
290297
}
291-
292-
/// Renders a Java function signature
293-
///
294-
/// `func method(x: Int, y: Int) -> Int` becomes
295-
/// `long method(long x, long y)`
296-
private func renderFunctionSignature(_ decl: ImportedFunc) -> String {
297-
guard let translatedDecl = translatedDecl(for: decl) else {
298-
fatalError("Unable to render function signature for a function that cannot be translated: \(decl)")
299-
}
300-
let resultType = translatedDecl.translatedFunctionSignature.resultType
301-
var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
302-
303-
if decl.isInitializer {
304-
parameters.append("SwiftArena swiftArena$")
305-
}
306-
307-
let throwsClause = decl.isThrowing ? " throws Exception" : ""
308-
309-
return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)"
310-
}
311298
}

0 commit comments

Comments
 (0)