Skip to content

Commit 7a75d45

Browse files
committed
import async functions using semaphores and completablefuture
1 parent 0869e55 commit 7a75d45

File tree

10 files changed

+468
-6
lines changed

10 files changed

+468
-6
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
import SwiftJava
16+
17+
public func asyncSum(i1: Int64, i2: Int64) async -> Int64 {
18+
try? await Task.sleep(for: .milliseconds(500))
19+
return i1 + i2
20+
}
21+
22+
public func asyncCopy(myClass: MySwiftClass) async -> MySwiftClass {
23+
let new = MySwiftClass(x: myClass.x, y: myClass.y)
24+
try? await Task.sleep(for: .milliseconds(500))
25+
return new
26+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
package com.example.swift;
16+
17+
import com.example.swift.MySwiftClass;
18+
import com.example.swift.MySwiftLibrary;
19+
import org.junit.jupiter.api.Test;
20+
import org.swift.swiftkit.core.SwiftArena;
21+
22+
import java.time.Duration;
23+
import java.util.Optional;
24+
import java.util.OptionalDouble;
25+
import java.util.OptionalInt;
26+
import java.util.OptionalLong;
27+
import java.util.concurrent.CompletableFuture;
28+
import java.util.concurrent.ExecutionException;
29+
30+
import static org.junit.jupiter.api.Assertions.*;
31+
32+
public class AsyncTest {
33+
@Test
34+
void asyncSum() {
35+
CompletableFuture<Long> future = MySwiftLibrary.asyncSum(10, 12);
36+
37+
Long result = future.join();
38+
assertEquals(22, result);
39+
}
40+
41+
@Test
42+
void asyncCopy() throws Exception {
43+
try (var arena = SwiftArena.ofConfined()) {
44+
MySwiftClass obj = MySwiftClass.init(10, 5, arena);
45+
CompletableFuture<MySwiftClass> future = MySwiftLibrary.asyncCopy(obj, arena);
46+
47+
MySwiftClass result = future.join();
48+
assertEquals(10, result.getX());
49+
assertEquals(5, result.getY());
50+
}
51+
}
52+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,12 @@ void addXWithJavaLong() {
152152
assertEquals(70, c1.addXWithJavaLong(javaLong));
153153
}
154154
}
155+
156+
@Test
157+
void getAsyncVariable() {
158+
try (var arena = SwiftArena.ofConfined()) {
159+
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
160+
assertEquals(42, c1.getGetAsync().join());
161+
}
162+
}
155163
}

Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,29 @@ extension JavaType {
111111
false
112112
}
113113
}
114+
115+
var wrapperClassIfNeeded: JavaType {
116+
switch self {
117+
case .boolean:
118+
return .class(package: nil, name: "Boolean")
119+
case .byte(let parameterAnnotations):
120+
return .class(package: nil, name: "Byte")
121+
case .char(let parameterAnnotations):
122+
return .class(package: nil, name: "Character")
123+
case .short(let parameterAnnotations):
124+
return .class(package: nil, name: "Short")
125+
case .int(let parameterAnnotations):
126+
return .class(package: nil, name: "Integer")
127+
case .long(let parameterAnnotations):
128+
return .class(package: nil, name: "Long")
129+
case .float:
130+
return .class(package: nil, name: "Float")
131+
case .double:
132+
return .class(package: nil, name: "Double")
133+
case .void:
134+
return .class(package: nil, name: "Void")
135+
default:
136+
return self
137+
}
138+
}
114139
}

Sources/JExtractSwiftLib/ImportedDecls.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
192192
self.functionSignature.effectSpecifiers.contains(.throws)
193193
}
194194

195+
var isAsync: Bool {
196+
self.functionSignature.effectSpecifiers.contains(.async)
197+
}
198+
195199
init(
196200
module: String,
197201
swiftDecl: any DeclSyntaxProtocol,

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ extension JNISwift2JavaGenerator {
429429
let translatedSignature = translatedDecl.translatedFunctionSignature
430430
let resultType = translatedSignature.resultType.javaType
431431
var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() }
432-
let throwsClause = translatedDecl.isThrowing ? " throws Exception" : ""
432+
let throwsClause = translatedDecl.isThrowing && !translatedDecl.isAsync ? " throws Exception" : ""
433433

434434
let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in
435435
guard case .generic(let name, let extends) = parameter.parameter.type else {
@@ -483,7 +483,6 @@ extension JNISwift2JavaGenerator {
483483

484484
printNativeFunction(&printer, translatedDecl)
485485
}
486-
487486
}
488487

489488
private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) {

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ extension JNISwift2JavaGenerator {
118118
name: getAsCaseName,
119119
isStatic: false,
120120
isThrowing: false,
121+
isAsync: false,
121122
nativeFunctionName: "$\(getAsCaseName)",
122123
parentName: enumName,
123124
functionTypes: [],
@@ -216,6 +217,7 @@ extension JNISwift2JavaGenerator {
216217
name: javaName,
217218
isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer,
218219
isThrowing: decl.isThrowing,
220+
isAsync: decl.isAsync,
219221
nativeFunctionName: "$\(javaName)",
220222
parentName: parentName,
221223
functionTypes: funcTypes,
@@ -279,7 +281,11 @@ extension JNISwift2JavaGenerator {
279281
genericRequirements: functionSignature.genericRequirements
280282
)
281283

282-
let resultType = try translate(swiftResult: functionSignature.result)
284+
var resultType = try translate(swiftResult: functionSignature.result)
285+
286+
if functionSignature.effectSpecifiers.contains(.async) {
287+
resultType = asyncResultConversion(result: resultType, mode: config.effectiveAsyncMode)
288+
}
283289

284290
return TranslatedFunctionSignature(
285291
selfParameter: selfParameter,
@@ -470,6 +476,35 @@ extension JNISwift2JavaGenerator {
470476
}
471477
}
472478

479+
func asyncResultConversion(
480+
result: TranslatedResult,
481+
mode: JExtractAsyncMode
482+
) -> TranslatedResult {
483+
switch mode {
484+
case .completableFuture:
485+
let supplyAsyncBodyConversion: JavaNativeConversionStep = if result.javaType.isVoid {
486+
.aggregate([
487+
.print(result.conversion),
488+
.null
489+
])
490+
} else {
491+
result.conversion
492+
}
493+
494+
return TranslatedResult(
495+
javaType: .class(package: "java.util.concurrent", name: "CompletableFuture<\(result.javaType.wrapperClassIfNeeded)>"),
496+
annotations: result.annotations,
497+
outParameters: result.outParameters,
498+
conversion: .method(.constant("java.util.concurrent.CompletableFuture"), function: "supplyAsync", arguments: [
499+
.lambda(body: supplyAsyncBodyConversion)
500+
])
501+
)
502+
503+
case .future:
504+
fatalError("TODO")
505+
}
506+
}
507+
473508
func translateProtocolParameter(
474509
protocolType: SwiftType,
475510
parameterName: String,
@@ -753,6 +788,8 @@ extension JNISwift2JavaGenerator {
753788

754789
let isThrowing: Bool
755790

791+
let isAsync: Bool
792+
756793
/// The name of the native function
757794
let nativeFunctionName: String
758795

@@ -912,6 +949,17 @@ extension JNISwift2JavaGenerator {
912949
/// Access a member of the value
913950
indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String)
914951

952+
/// `return value`
953+
indirect case `return`(JavaNativeConversionStep)
954+
955+
/// `() -> { return body; }`
956+
indirect case lambda(body: JavaNativeConversionStep)
957+
958+
case null
959+
960+
/// Prints the conversion step, ignoring the output.
961+
indirect case print(JavaNativeConversionStep)
962+
915963
/// Returns the conversion string applied to the placeholder.
916964
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
917965
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
@@ -1024,13 +1072,37 @@ extension JNISwift2JavaGenerator {
10241072

10251073
case .replacingPlaceholder(let inner, let placeholder):
10261074
return inner.render(&printer, placeholder)
1075+
1076+
case .return(let inner):
1077+
let inner = inner.render(&printer, placeholder)
1078+
return "return \(inner);"
1079+
1080+
case .lambda(let body):
1081+
var printer = CodePrinter()
1082+
printer.printBraceBlock("() ->") { printer in
1083+
let body = body.render(&printer, placeholder)
1084+
if !body.isEmpty {
1085+
printer.print("return \(body);")
1086+
} else {
1087+
printer.print("return;")
1088+
}
1089+
}
1090+
return printer.finalize()
1091+
1092+
case .null:
1093+
return "null"
1094+
1095+
case .print(let inner):
1096+
let inner = inner.render(&printer, placeholder)
1097+
printer.print("\(inner);")
1098+
return ""
10271099
}
10281100
}
10291101

10301102
/// Whether the conversion uses SwiftArena.
10311103
var requiresSwiftArena: Bool {
10321104
switch self {
1033-
case .placeholder, .constant, .isOptionalPresent, .combinedName:
1105+
case .placeholder, .constant, .isOptionalPresent, .combinedName, .null:
10341106
return false
10351107

10361108
case .constructSwiftValue, .wrapMemoryAddressUnsafe:
@@ -1074,6 +1146,15 @@ extension JNISwift2JavaGenerator {
10741146

10751147
case .replacingPlaceholder(let inner, _):
10761148
return inner.requiresSwiftArena
1149+
1150+
case .return(let inner):
1151+
return inner.requiresSwiftArena
1152+
1153+
case .lambda(let body):
1154+
return body.requiresSwiftArena
1155+
1156+
case .print(let inner):
1157+
return inner.requiresSwiftArena
10771158
}
10781159
}
10791160
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,20 @@ extension JNISwift2JavaGenerator {
5959
nil
6060
}
6161

62-
return try NativeFunctionSignature(
62+
var result = try translate(swiftResult: functionSignature.result)
63+
64+
if functionSignature.effectSpecifiers.contains(.async) {
65+
result = asyncResultConversion(
66+
result: result,
67+
functionSignature: functionSignature,
68+
mode: config.effectiveAsyncMode
69+
)
70+
}
71+
72+
return NativeFunctionSignature(
6373
selfParameter: nativeSelf,
6474
parameters: parameters,
65-
result: translate(swiftResult: functionSignature.result)
75+
result: result
6676
)
6777
}
6878

@@ -504,6 +514,27 @@ extension JNISwift2JavaGenerator {
504514
throw JavaTranslationError.unsupportedSwiftType(swiftResult.type)
505515
}
506516
}
517+
518+
func asyncResultConversion(
519+
result: NativeResult,
520+
functionSignature: SwiftFunctionSignature,
521+
mode: JExtractAsyncMode
522+
) -> NativeResult {
523+
switch mode {
524+
case .completableFuture:
525+
return NativeResult(
526+
javaType: result.javaType,
527+
conversion: .asyncBlocking(
528+
result.conversion,
529+
swiftFunctionResultType: functionSignature.result.type
530+
),
531+
outParameters: result.outParameters
532+
)
533+
534+
case .future:
535+
fatalError("TODO")
536+
}
537+
}
507538
}
508539

509540
struct NativeFunctionSignature {
@@ -588,6 +619,8 @@ extension JNISwift2JavaGenerator {
588619

589620
indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String)
590621

622+
indirect case asyncBlocking(NativeSwiftConversionStep, swiftFunctionResultType: SwiftType)
623+
591624
/// Returns the conversion string applied to the placeholder.
592625
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
593626
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
@@ -815,6 +848,26 @@ extension JNISwift2JavaGenerator {
815848
"""
816849
)
817850
return unwrappedName
851+
852+
case .asyncBlocking(let inner, let swiftFunctionResultType):
853+
printer.print("let _semaphore$ = SwiftJava._Semaphore(value: 0)")
854+
printer.print("var swiftResult$: \(swiftFunctionResultType)!")
855+
856+
printer.printBraceBlock("Task") { printer in
857+
printer.print(
858+
"""
859+
swiftResult$ = await \(placeholder)
860+
_semaphore$.signal()
861+
"""
862+
)
863+
}
864+
printer.print(
865+
"""
866+
_semaphore$.wait()
867+
"""
868+
)
869+
let inner = inner.render(&printer, "swiftResult$")
870+
return inner
818871
}
819872
}
820873
}

0 commit comments

Comments
 (0)