Skip to content

Commit 9aa4dca

Browse files
committed
work on importing closures
1 parent a25d216 commit 9aa4dca

File tree

9 files changed

+415
-50
lines changed

9 files changed

+415
-50
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
}

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,13 +175,68 @@ extension JNISwift2JavaGenerator {
175175
}
176176
}
177177

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

184-
var modifiers = ["public"]
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+
239+
var modifiers = "public"
185240
if decl.isStatic || decl.isInitializer || !decl.hasParent {
186241
modifiers.append("static")
187242
}
@@ -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
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 99 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ extension JNISwift2JavaGenerator {
2424

2525
let translated: TranslatedFunctionDecl?
2626
do {
27-
let translation = JavaTranslation(swiftModuleName: swiftModuleName)
27+
let translation = JavaTranslation(swiftModuleName: swiftModuleName, javaPackage: self.javaPackage)
2828
translated = try translation.translate(decl)
2929
} catch {
3030
self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)")
@@ -37,17 +37,10 @@ extension JNISwift2JavaGenerator {
3737

3838
struct JavaTranslation {
3939
let swiftModuleName: String
40+
let javaPackage: String
4041

4142
func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl {
42-
let nativeTranslation = NativeJavaTranslation()
43-
44-
// Swift -> Java
45-
let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature)
46-
// Java -> Java (native)
47-
let nativeFunctionSignature = try nativeTranslation.translate(
48-
functionSignature: decl.functionSignature,
49-
translatedFunctionSignature: translatedFunctionSignature
50-
)
43+
let nativeTranslation = NativeJavaTranslation(javaPackage: self.javaPackage)
5144

5245
// Types with no parent will be outputted inside a "module" class.
5346
let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName
@@ -59,27 +52,91 @@ extension JNISwift2JavaGenerator {
5952
case .function, .initializer: decl.name
6053
}
6154

55+
// Swift -> Java
56+
let translatedFunctionSignature = try translate(
57+
functionSignature: decl.functionSignature,
58+
methodName: javaName,
59+
parentName: parentName
60+
)
61+
// Java -> Java (native)
62+
let nativeFunctionSignature = try nativeTranslation.translate(
63+
functionSignature: decl.functionSignature,
64+
translatedFunctionSignature: translatedFunctionSignature,
65+
methodName: javaName,
66+
parentName: parentName
67+
)
68+
69+
// Closures.
70+
var funcTypes: [TranslatedFunctionType] = []
71+
for (idx, param) in decl.functionSignature.parameters.enumerated() {
72+
let parameterName = param.parameterName ?? "_\(idx)"
73+
74+
switch param.type {
75+
case .function(let funcTy):
76+
let translatedClosure = try translateFunctionType(
77+
name: parameterName,
78+
swiftType: funcTy,
79+
parentName: parentName
80+
)
81+
funcTypes.append(translatedClosure)
82+
default:
83+
break
84+
}
85+
}
86+
6287
return TranslatedFunctionDecl(
6388
name: javaName,
6489
nativeFunctionName: "$\(javaName)",
6590
parentName: parentName,
91+
functionTypes: funcTypes,
6692
translatedFunctionSignature: translatedFunctionSignature,
6793
nativeFunctionSignature: nativeFunctionSignature
6894
)
6995
}
7096

71-
func translate(functionSignature: SwiftFunctionSignature) throws -> TranslatedFunctionSignature {
97+
/// Translate Swift closure type to Java functional interface.
98+
func translateFunctionType(
99+
name: String,
100+
swiftType: SwiftFunctionType,
101+
parentName: String
102+
) throws -> TranslatedFunctionType {
103+
var translatedParams: [TranslatedParameter] = []
104+
105+
for (i, param) in swiftType.parameters.enumerated() {
106+
let paramName = param.parameterName ?? "_\(i)"
107+
translatedParams.append(
108+
try translateParameter(swiftType: param.type, parameterName: paramName, methodName: name, parentName: parentName)
109+
)
110+
}
111+
112+
let transltedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType))
113+
114+
return TranslatedFunctionType(
115+
name: name,
116+
parameters: translatedParams,
117+
result: transltedResult,
118+
swiftType: swiftType
119+
)
120+
}
121+
122+
func translate(
123+
functionSignature: SwiftFunctionSignature,
124+
methodName: String,
125+
parentName: String
126+
) throws -> TranslatedFunctionSignature {
72127
let parameters = try functionSignature.parameters.enumerated().map { idx, param in
73128
let parameterName = param.parameterName ?? "arg\(idx))"
74-
return try translateParameter(swiftType: param.type, parameterName: parameterName)
129+
return try translateParameter(swiftType: param.type, parameterName: parameterName, methodName: methodName, parentName: parentName)
75130
}
76131

77132
// 'self'
78133
let selfParameter: TranslatedParameter?
79134
if case .instance(let swiftSelf) = functionSignature.selfParameter {
80135
selfParameter = try self.translateParameter(
81136
swiftType: swiftSelf.type,
82-
parameterName: swiftSelf.parameterName ?? "self"
137+
parameterName: swiftSelf.parameterName ?? "self",
138+
methodName: methodName,
139+
parentName: parentName
83140
)
84141
} else {
85142
selfParameter = nil
@@ -92,7 +149,12 @@ extension JNISwift2JavaGenerator {
92149
)
93150
}
94151

95-
func translateParameter(swiftType: SwiftType, parameterName: String) throws -> TranslatedParameter {
152+
func translateParameter(
153+
swiftType: SwiftType,
154+
parameterName: String,
155+
methodName: String,
156+
parentName: String
157+
) throws -> TranslatedParameter {
96158
switch swiftType {
97159
case .nominal(let nominalType):
98160
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
@@ -121,7 +183,16 @@ extension JNISwift2JavaGenerator {
121183
conversion: .placeholder
122184
)
123185

124-
case .metatype, .optional, .tuple, .function, .existential, .opaque:
186+
case .function:
187+
return TranslatedParameter(
188+
parameter: JavaParameter(
189+
name: parameterName,
190+
type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)")
191+
),
192+
conversion: .placeholder
193+
)
194+
195+
case .metatype, .optional, .tuple, .existential, .opaque:
125196
throw JavaTranslationError.unsupportedSwiftType(swiftType)
126197
}
127198
}
@@ -168,6 +239,9 @@ extension JNISwift2JavaGenerator {
168239
/// The name of the Java parent scope this function is declared in
169240
let parentName: String
170241

242+
/// Functional interfaces required for the Java method.
243+
let functionTypes: [TranslatedFunctionType]
244+
171245
/// Function signature of the Java function the user will call
172246
let translatedFunctionSignature: TranslatedFunctionSignature
173247

@@ -220,6 +294,16 @@ extension JNISwift2JavaGenerator {
220294
let conversion: JavaNativeConversionStep
221295
}
222296

297+
/// Represent a Swift closure type in the user facing Java API.
298+
///
299+
/// Closures are translated to named functional interfaces in Java.
300+
struct TranslatedFunctionType {
301+
var name: String
302+
var parameters: [TranslatedParameter]
303+
var result: TranslatedResult
304+
var swiftType: SwiftFunctionType
305+
}
306+
223307
/// Describes how to convert values between Java types and the native Java function
224308
enum JavaNativeConversionStep {
225309
/// The value being converted

0 commit comments

Comments
 (0)