Skip to content

Commit f62a317

Browse files
authored
[JExtract/JNI] Add support for primitive non-escaping closures. (#327)
1 parent a25d216 commit f62a317

File tree

11 files changed

+556
-50
lines changed

11 files changed

+556
-50
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
public func emptyClosure(closure: () -> ()) {
16+
closure()
17+
}
18+
19+
public func closureWithInt(input: Int64, closure: (Int64) -> Int64) -> Int64 {
20+
return closure(input)
21+
}
22+
23+
public func closureMultipleArguments(
24+
input1: Int64,
25+
input2: Int64,
26+
closure: (Int64, Int64) -> Int64
27+
) -> Int64 {
28+
return closure(input1, input2)
29+
}
30+
31+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package com.example.swift;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
import java.util.concurrent.atomic.AtomicBoolean;
20+
21+
import static org.junit.jupiter.api.Assertions.*;
22+
23+
public class ClosuresTest {
24+
@Test
25+
void emptyClosure() {
26+
AtomicBoolean closureCalled = new AtomicBoolean(false);
27+
MySwiftLibrary.emptyClosure(() -> {
28+
closureCalled.set(true);
29+
});
30+
assertTrue(closureCalled.get());
31+
}
32+
33+
@Test
34+
void closureWithInt() {
35+
long result = MySwiftLibrary.closureWithInt(10, (value) -> value * 2);
36+
assertEquals(20, result);
37+
}
38+
39+
@Test
40+
void closureMultipleArguments() {
41+
long result = MySwiftLibrary.closureMultipleArguments(5, 10, (a, b) -> a + b);
42+
assertEquals(15, result);
43+
}
44+
}

Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,22 @@ import JavaTypes
1717
extension JavaType {
1818
var jniTypeSignature: String {
1919
switch self {
20-
case .boolean: "Z"
21-
case .byte: "B"
22-
case .char: "C"
23-
case .short: "S"
24-
case .int: "I"
25-
case .long: "J"
26-
case .float: "F"
27-
case .double: "D"
20+
case .boolean: return "Z"
21+
case .byte: return "B"
22+
case .char: return "C"
23+
case .short: return "S"
24+
case .int: return "I"
25+
case .long: return "J"
26+
case .float: return "F"
27+
case .double: return "D"
2828
case .class(let package, let name):
29+
let nameWithInnerClasses = name.replacingOccurrences(of: ".", with: "$")
2930
if let package {
30-
"L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);"
31+
return "L\(package.replacingOccurrences(of: ".", with: "/"))/\(nameWithInnerClasses);"
3132
} else {
32-
"L\(name);"
33+
return "L\(nameWithInnerClasses);"
3334
}
34-
case .array(let javaType): "[\(javaType.jniTypeSignature)"
35+
case .array(let javaType): return "[\(javaType.jniTypeSignature)"
3536
case .void: fatalError("There is no type signature for 'void'")
3637
}
3738
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ extension JNISwift2JavaGenerator {
7676

7777
for decl in analysis.importedGlobalFuncs {
7878
self.logger.trace("Print global function: \(decl)")
79-
printFunctionBinding(&printer, decl)
79+
printFunctionDowncallMethods(&printer, decl)
8080
printer.println()
8181
}
8282

8383
for decl in analysis.importedGlobalVariables {
84-
printFunctionBinding(&printer, decl)
84+
printFunctionDowncallMethods(&printer, decl)
8585
printer.println()
8686
}
8787
}
@@ -117,17 +117,17 @@ extension JNISwift2JavaGenerator {
117117
printer.println()
118118

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

124124
for method in decl.methods {
125-
printFunctionBinding(&printer, method)
125+
printFunctionDowncallMethods(&printer, method)
126126
printer.println()
127127
}
128128

129129
for variable in decl.variables {
130-
printFunctionBinding(&printer, variable)
130+
printFunctionDowncallMethods(&printer, variable)
131131
printer.println()
132132
}
133133

@@ -175,12 +175,67 @@ extension JNISwift2JavaGenerator {
175175
}
176176
}
177177

178-
private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
179-
guard let translatedDecl = translatedDecl(for: decl) else {
178+
private func printFunctionDowncallMethods(
179+
_ printer: inout CodePrinter,
180+
_ decl: ImportedFunc
181+
) {
182+
guard translatedDecl(for: decl) != nil else {
180183
// Failed to translate. Skip.
181184
return
182185
}
183186

187+
printer.printSeparator(decl.displayName)
188+
189+
printJavaBindingWrapperHelperClass(&printer, decl)
190+
191+
printJavaBindingWrapperMethod(&printer, decl)
192+
}
193+
194+
/// Print the helper type container for a user-facing Java API.
195+
///
196+
/// * User-facing functional interfaces.
197+
private func printJavaBindingWrapperHelperClass(
198+
_ printer: inout CodePrinter,
199+
_ decl: ImportedFunc
200+
) {
201+
let translated = self.translatedDecl(for: decl)!
202+
if translated.functionTypes.isEmpty {
203+
return
204+
}
205+
206+
printer.printBraceBlock(
207+
"""
208+
public static class \(translated.name)
209+
"""
210+
) { printer in
211+
for functionType in translated.functionTypes {
212+
printJavaBindingWrapperFunctionTypeHelper(&printer, functionType)
213+
}
214+
}
215+
}
216+
217+
/// Print "wrapper" functional interface representing a Swift closure type.
218+
func printJavaBindingWrapperFunctionTypeHelper(
219+
_ printer: inout CodePrinter,
220+
_ functionType: TranslatedFunctionType
221+
) {
222+
let apiParams = functionType.parameters.map(\.parameter.asParameter)
223+
224+
printer.print(
225+
"""
226+
@FunctionalInterface
227+
public interface \(functionType.name) {
228+
\(functionType.result.javaType) apply(\(apiParams.joined(separator: ", ")));
229+
}
230+
"""
231+
)
232+
}
233+
234+
private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
235+
guard let translatedDecl = translatedDecl(for: decl) else {
236+
fatalError("Decl was not translated, \(decl)")
237+
}
238+
184239
var modifiers = ["public"]
185240
if decl.isStatic || decl.isInitializer || !decl.hasParent {
186241
modifiers.append("static")
@@ -215,7 +270,7 @@ extension JNISwift2JavaGenerator {
215270
if let selfParameter = nativeSignature.selfParameter {
216271
parameters.append(selfParameter)
217272
}
218-
let renderedParameters = parameters.map { "\($0.javaParameter.type) \($0.javaParameter.name)"}.joined(separator: ", ")
273+
let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ")
219274

220275
printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));")
221276
}

0 commit comments

Comments
 (0)