Skip to content

Commit 45dc84e

Browse files
committed
generate code for member methods
1 parent ac80400 commit 45dc84e

File tree

6 files changed

+209
-47
lines changed

6 files changed

+209
-47
lines changed

Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,20 @@ public class MySwiftClass {
3434
deinit {
3535
p("deinit called!")
3636
}
37+
38+
public func sum() -> Int64 {
39+
return x + y
40+
}
41+
42+
public func xMultiplied(by z: Int64) -> Int64 {
43+
return x * z;
44+
}
45+
46+
enum MySwiftClassError: Error {
47+
case swiftError
48+
}
49+
50+
public func throwingFunction() throws {
51+
throw MySwiftClassError.swiftError
52+
}
3753
}

Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ static void examples() {
4444
try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) {
4545
MySwiftClass myClass = MySwiftClass.init(10, 5, arena);
4646
MySwiftClass myClass2 = MySwiftClass.init(arena);
47+
48+
try {
49+
myClass.throwingFunction();
50+
} catch (Exception e) {
51+
System.out.println("Caught exception: " + e.getMessage());
52+
}
4753
}
4854

4955
System.out.println("DONE.");

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,30 @@ void init_withParameters() {
3535
assertNotNull(c);
3636
}
3737
}
38+
39+
@Test
40+
void sum() {
41+
try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) {
42+
MySwiftClass c = MySwiftClass.init(20, 10, arena);
43+
assertEquals(30, c.sum());
44+
}
45+
}
46+
47+
@Test
48+
void xMultiplied() {
49+
try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) {
50+
MySwiftClass c = MySwiftClass.init(20, 10, arena);
51+
assertEquals(200, c.xMultiplied(10));
52+
}
53+
}
54+
55+
@Test
56+
void throwingFunction() {
57+
try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) {
58+
MySwiftClass c = MySwiftClass.init(20, 10, arena);
59+
Exception exception = assertThrows(Exception.class, () -> c.throwingFunction());
60+
61+
assertEquals("swiftError", exception.getMessage());
62+
}
63+
}
3864
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,49 @@ extension JNISwift2JavaGenerator {
164164
}
165165

166166
private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
167+
if decl.isStatic || decl.isInitializer || !decl.hasParent {
168+
printStaticFunctionBinding(&printer, decl)
169+
} else {
170+
printMemberMethodBindings(&printer, decl)
171+
}
172+
}
173+
174+
private func printStaticFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
167175
printDeclDocumentation(&printer, decl)
168176
printer.print(
169177
"public static native \(renderFunctionSignature(decl));"
170178
)
171179
}
172180

181+
/// Renders Java bindings for member methods
182+
///
183+
/// Member methods are generated as a function that extracts the `selfPointer`
184+
/// and passes it down to another native function along with the arguments
185+
/// to call the Swift implementation.
186+
private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
187+
let translatedDecl = translatedDecl(for: decl)
188+
189+
printDeclDocumentation(&printer, decl)
190+
printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in
191+
var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name)
192+
arguments.append("selfPointer")
193+
194+
let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return "
195+
196+
printer.print(
197+
"""
198+
long selfPointer = this.pointer();
199+
\(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", ")));
200+
"""
201+
)
202+
}
203+
204+
let returnType = translatedDecl.translatedFunctionSignature.resultType
205+
var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
206+
parameters.append("long selfPointer")
207+
printer.print("private static native \(returnType) $\(translatedDecl.name)(\(parameters.joined(separator: ", ")));")
208+
}
209+
173210
private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) {
174211
let translatedDecl = translatedDecl(for: decl)
175212

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -136,67 +136,97 @@ extension JNISwift2JavaGenerator {
136136
_ printer: inout CodePrinter,
137137
_ decl: ImportedFunc
138138
) {
139+
// Free functions does not have a parent
140+
if decl.isStatic || !decl.hasParent {
141+
self.printSwiftStaticFunctionThunk(&printer, decl)
142+
} else {
143+
self.printSwiftMemberFunctionThunk(&printer, decl)
144+
}
145+
}
146+
147+
private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
139148
let translatedDecl = self.translatedDecl(for: decl)
140-
let parentName = translatedDecl.parentName
141-
let swiftReturnType = decl.functionSignature.result.type
142149

143-
printCDecl(&printer, decl) { printer in
144-
let downcallParameters = renderDowncallArguments(
145-
swiftFunctionSignature: decl.functionSignature,
146-
translatedFunctionSignature: translatedDecl.translatedFunctionSignature
150+
printCDecl(
151+
&printer,
152+
javaMethodName: translatedDecl.name,
153+
parentName: translatedDecl.parentName,
154+
parameters: translatedDecl.translatedFunctionSignature.parameters,
155+
isStatic: true,
156+
resultType: translatedDecl.translatedFunctionSignature.resultType
157+
) { printer in
158+
// For free functions the parent is the Swift module
159+
let parentName = decl.parentType?.asNominalTypeDeclaration?.qualifiedName ?? swiftModuleName
160+
self.printFunctionDowncall(&printer, decl, calleeName: parentName)
161+
}
162+
}
163+
164+
private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
165+
let translatedDecl = self.translatedDecl(for: decl)
166+
let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName
167+
168+
printCDecl(
169+
&printer,
170+
javaMethodName: "$\(translatedDecl.name)",
171+
parentName: translatedDecl.parentName,
172+
parameters: translatedDecl.translatedFunctionSignature.parameters + [
173+
JavaParameter(name: "selfPointer", type: .long)
174+
],
175+
isStatic: true,
176+
resultType: translatedDecl.translatedFunctionSignature.resultType
177+
) { printer in
178+
printer.print(
179+
"""
180+
let self$ = UnsafeMutablePointer<\(swiftParentName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))!
181+
"""
147182
)
148-
let tryClause: String = decl.isThrowing ? "try " : ""
149-
let functionDowncall =
150-
"\(tryClause)\(parentName).\(decl.name)(\(downcallParameters))"
151-
152-
let innerBody =
153-
if swiftReturnType.isVoid {
154-
functionDowncall
155-
} else {
156-
"""
157-
let result = \(functionDowncall)
158-
return result.getJNIValue(in: environment)
159-
"""
160-
}
183+
self.printFunctionDowncall(&printer, decl, calleeName: "self$.pointee")
184+
}
185+
}
186+
187+
private func printFunctionDowncall(
188+
_ printer: inout CodePrinter,
189+
_ decl: ImportedFunc,
190+
calleeName: String
191+
) {
192+
let translatedDecl = self.translatedDecl(for: decl)
193+
let swiftReturnType = decl.functionSignature.result.type
194+
195+
let downcallParameters = renderDowncallArguments(
196+
swiftFunctionSignature: decl.functionSignature,
197+
translatedFunctionSignature: translatedDecl.translatedFunctionSignature
198+
)
199+
let tryClause: String = decl.isThrowing ? "try " : ""
200+
let functionDowncall = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))"
201+
202+
let returnStatement =
203+
if swiftReturnType.isVoid {
204+
functionDowncall
205+
} else {
206+
"""
207+
let result = \(functionDowncall)
208+
return result.getJNIValue(in: environment)
209+
"""
210+
}
161211

162-
if decl.isThrowing {
163-
let dummyReturn =
164-
!swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : ""
165-
printer.print(
212+
if decl.isThrowing {
213+
let dummyReturn =
214+
!swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : ""
215+
printer.print(
166216
"""
167217
do {
168-
\(innerBody)
218+
\(returnStatement)
169219
} catch {
170220
environment.throwAsException(error)
171221
\(dummyReturn)
172222
}
173223
"""
174-
)
175-
} else {
176-
printer.print(innerBody)
177-
}
224+
)
225+
} else {
226+
printer.print(returnStatement)
178227
}
179228
}
180229

181-
private func printCDecl(
182-
_ printer: inout CodePrinter,
183-
_ decl: ImportedFunc,
184-
_ body: (inout CodePrinter) -> Void
185-
) {
186-
let translatedDecl = translatedDecl(for: decl)
187-
let parentName = translatedDecl.parentName
188-
189-
printCDecl(
190-
&printer,
191-
javaMethodName: translatedDecl.name,
192-
parentName: parentName,
193-
parameters: translatedDecl.translatedFunctionSignature.parameters,
194-
isStatic: decl.isStatic || decl.isInitializer || !decl.hasParent,
195-
resultType: translatedDecl.translatedFunctionSignature.resultType,
196-
body
197-
)
198-
}
199-
200230
private func printCDecl(
201231
_ printer: inout CodePrinter,
202232
javaMethodName: String,

Tests/JExtractSwiftTests/JNI/JNIClassTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ struct JNIClassTests {
3333
self.x = 0
3434
self.y = 0
3535
}
36+
37+
public func doSomething(x: Int64) {}
3638
}
3739
"""
3840

@@ -206,4 +208,49 @@ struct JNIClassTests {
206208
]
207209
)
208210
}
211+
212+
@Test
213+
func memberMethod_javaBindings() throws {
214+
try assertOutput(
215+
input: source,
216+
.jni,
217+
.java,
218+
expectedChunks: [
219+
"""
220+
/**
221+
* Downcall to Swift:
222+
* {@snippet lang=swift :
223+
* public func doSomething(x: Int64)
224+
* }
225+
*/
226+
public void doSomething(long x) {
227+
long selfPointer = this.pointer();
228+
MyClass.$doSomething(x, selfPointer);
229+
}
230+
""",
231+
"""
232+
private static native void $doSomething(long x, long selfPointer);
233+
"""
234+
]
235+
)
236+
}
237+
238+
@Test
239+
func memberMethod_swiftThunks() throws {
240+
try assertOutput(
241+
input: source,
242+
.jni,
243+
.swift,
244+
detectChunkByInitialLines: 1,
245+
expectedChunks: [
246+
"""
247+
@_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ")
248+
func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, x: jlong, selfPointer: jlong) {
249+
let self$ = UnsafeMutablePointer<MyClass>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))!
250+
self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!))
251+
}
252+
""",
253+
]
254+
)
255+
}
209256
}

0 commit comments

Comments
 (0)