Skip to content

Commit 2012bd1

Browse files
committed
work on importing closures
1 parent dfac323 commit 2012bd1

File tree

9 files changed

+414
-49
lines changed

9 files changed

+414
-49
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: 62 additions & 7 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) {
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

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")
@@ -213,7 +268,7 @@ extension JNISwift2JavaGenerator {
213268
if let selfParameter = nativeSignature.selfParameter {
214269
parameters.append(selfParameter)
215270
}
216-
let renderedParameters = parameters.map { "\($0.javaParameter.type) \($0.javaParameter.name)"}.joined(separator: ", ")
271+
let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ")
217272

218273
printer.print("private static native \(resultType) \(nativeName)(\(renderedParameters));")
219274
}

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,26 +52,90 @@ 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
parentName: parentName,
90+
functionTypes: funcTypes,
6591
translatedFunctionSignature: translatedFunctionSignature,
6692
nativeFunctionSignature: nativeFunctionSignature
6793
)
6894
}
6995

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

76131
// 'self'
77132
let selfParameter: TranslatedParameter?
78133
if case .instance(let swiftSelf) = functionSignature.selfParameter {
79134
selfParameter = try self.translateParameter(
80135
swiftType: swiftSelf.type,
81-
parameterName: swiftSelf.parameterName ?? "self"
136+
parameterName: swiftSelf.parameterName ?? "self",
137+
methodName: methodName,
138+
parentName: parentName
82139
)
83140
} else {
84141
selfParameter = nil
@@ -91,7 +148,12 @@ extension JNISwift2JavaGenerator {
91148
)
92149
}
93150

94-
func translateParameter(swiftType: SwiftType, parameterName: String) throws -> TranslatedParameter {
151+
func translateParameter(
152+
swiftType: SwiftType,
153+
parameterName: String,
154+
methodName: String,
155+
parentName: String
156+
) throws -> TranslatedParameter {
95157
switch swiftType {
96158
case .nominal(let nominalType):
97159
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
@@ -120,7 +182,16 @@ extension JNISwift2JavaGenerator {
120182
conversion: .placeholder
121183
)
122184

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

238+
/// Functional interfaces required for the Java method.
239+
let functionTypes: [TranslatedFunctionType]
240+
167241
/// Function signature of the Java function the user will call
168242
let translatedFunctionSignature: TranslatedFunctionSignature
169243

@@ -216,6 +290,16 @@ extension JNISwift2JavaGenerator {
216290
let conversion: JavaNativeConversionStep
217291
}
218292

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

0 commit comments

Comments
 (0)