Skip to content

Commit d8db270

Browse files
committed
add support for async throws
1 parent 7a75d45 commit d8db270

File tree

7 files changed

+81
-32
lines changed

7 files changed

+81
-32
lines changed

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public func asyncSum(i1: Int64, i2: Int64) async -> Int64 {
1919
return i1 + i2
2020
}
2121

22-
public func asyncCopy(myClass: MySwiftClass) async -> MySwiftClass {
22+
public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass {
2323
let new = MySwiftClass(x: myClass.x, y: myClass.y)
24-
try? await Task.sleep(for: .milliseconds(500))
24+
try await Task.sleep(for: .milliseconds(500))
2525
return new
2626
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
enum MySwiftError: Error {
16+
case swiftError
17+
}

Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public func optionalJavaKitLong(input: Optional<JavaLong>) -> Int64? {
6262
}
6363
}
6464

65+
public func optionalThrowing() throws -> Int64? {
66+
throw MySwiftError.swiftError
67+
}
68+
6569
public func multipleOptionals(
6670
input1: Optional<Int8>,
6771
input2: Optional<Int16>,

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.example.swift;
1616

17+
import com.example.swift.MySwiftLibrary;
1718
import org.junit.jupiter.api.Test;
1819
import org.swift.swiftkit.core.SwiftArena;
1920

@@ -22,8 +23,7 @@
2223
import java.util.OptionalInt;
2324
import java.util.OptionalLong;
2425

25-
import static org.junit.jupiter.api.Assertions.assertEquals;
26-
import static org.junit.jupiter.api.Assertions.assertTrue;
26+
import static org.junit.jupiter.api.Assertions.*;
2727

2828
public class OptionalsTest {
2929
@Test
@@ -113,4 +113,13 @@ void multipleOptionals() {
113113
assertEquals(result, OptionalLong.of(1L));
114114
}
115115
}
116+
117+
@Test
118+
void optionalThrows() {
119+
try (var arena = SwiftArena.ofConfined()) {
120+
Exception exception = assertThrows(Exception.class, () -> MySwiftLibrary.optionalThrowing());
121+
122+
assertEquals("swiftError", exception.getMessage());
123+
}
124+
}
116125
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ extension JNISwift2JavaGenerator {
526526
javaType: result.javaType,
527527
conversion: .asyncBlocking(
528528
result.conversion,
529+
isThrowing: functionSignature.effectSpecifiers.contains(.throws),
529530
swiftFunctionResultType: functionSignature.result.type
530531
),
531532
outParameters: result.outParameters
@@ -619,7 +620,7 @@ extension JNISwift2JavaGenerator {
619620

620621
indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String)
621622

622-
indirect case asyncBlocking(NativeSwiftConversionStep, swiftFunctionResultType: SwiftType)
623+
indirect case asyncBlocking(NativeSwiftConversionStep, isThrowing: Bool, swiftFunctionResultType: SwiftType)
623624

624625
/// Returns the conversion string applied to the placeholder.
625626
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
@@ -849,24 +850,30 @@ extension JNISwift2JavaGenerator {
849850
)
850851
return unwrappedName
851852

852-
case .asyncBlocking(let inner, let swiftFunctionResultType):
853+
case .asyncBlocking(let inner, let isThrowing, let swiftFunctionResultType):
853854
printer.print("let _semaphore$ = SwiftJava._Semaphore(value: 0)")
854-
printer.print("var swiftResult$: \(swiftFunctionResultType)!")
855+
let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description
856+
printer.print("var swiftResult$: \(resultType)!")
855857

856858
printer.printBraceBlock("Task") { printer in
857-
printer.print(
858-
"""
859-
swiftResult$ = await \(placeholder)
860-
_semaphore$.signal()
861-
"""
862-
)
859+
if isThrowing {
860+
printer.printBraceBlock("do") { printer in
861+
printer.print("swiftResult$ = await Result.success(\(placeholder))")
862+
}
863+
printer.printBraceBlock("catch") { printer in
864+
printer.print("swiftResult$ = Result.failure(error)")
865+
}
866+
} else {
867+
printer.print("swiftResult$ = await \(placeholder)")
868+
}
869+
printer.print("_semaphore$.signal()")
863870
}
864871
printer.print(
865872
"""
866873
_semaphore$.wait()
867874
"""
868875
)
869-
let inner = inner.render(&printer, "swiftResult$")
876+
let inner = inner.render(&printer, isThrowing ? "try swiftResult$.get()" : "swiftResult$")
870877
return inner
871878
}
872879
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -353,29 +353,41 @@ extension JNISwift2JavaGenerator {
353353
}
354354

355355
// Lower the result.
356-
let innerBody: String
357-
if !decl.functionSignature.result.type.isVoid {
358-
let loweredResult = nativeSignature.result.conversion.render(&printer, result)
359-
innerBody = "return \(loweredResult)"
360-
} else {
361-
innerBody = result
356+
func innerBody(in printer: inout CodePrinter) -> String {
357+
if !decl.functionSignature.result.type.isVoid {
358+
let loweredResult = nativeSignature.result.conversion.render(&printer, result)
359+
return "return \(loweredResult)"
360+
} else {
361+
return result
362+
}
362363
}
363364

364365
if decl.isThrowing {
365-
// TODO: Handle classes for dummy value
366-
let dummyReturn = !nativeSignature.result.javaType.isVoid ? "return \(decl.functionSignature.result.type).jniPlaceholderValue" : ""
366+
let dummyReturn: String
367+
368+
if nativeSignature.result.javaType.isVoid {
369+
dummyReturn = ""
370+
} else {
371+
// We assume it is something that implements JavaValue
372+
dummyReturn = "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue"
373+
}
374+
375+
printer.print("do {")
376+
printer.indent()
377+
printer.print(innerBody(in: &printer))
378+
printer.outdent()
379+
printer.print("} catch {")
380+
printer.indent()
367381
printer.print(
368-
"""
369-
do {
370-
\(innerBody)
371-
} catch {
372-
environment.throwAsException(error)
373-
\(dummyReturn)
374-
}
375-
"""
382+
"""
383+
environment.throwAsException(error)
384+
\(dummyReturn)
385+
"""
376386
)
387+
printer.outdent()
388+
printer.print("}")
377389
} else {
378-
printer.print(innerBody)
390+
printer.print(innerBody(in: &printer))
379391
}
380392
}
381393

Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ extension SwiftFunctionSignature {
245245
if signature.effectSpecifiers?.throwsClause != nil {
246246
effectSpecifiers.append(.throws)
247247
}
248-
if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier {
248+
if signature.effectSpecifiers?.asyncSpecifier != nil {
249249
effectSpecifiers.append(.async)
250250
}
251251

0 commit comments

Comments
 (0)