Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,15 @@ let package = Package(
// Support library written in Swift for SwiftKit "Java"
.library(
name: "SwiftJavaRuntimeSupport",
type: .dynamic,
targets: ["SwiftJavaRuntimeSupport"]
),

.library(
name: "SwiftRuntimeFunctions",
type: .dynamic,
targets: ["SwiftRuntimeFunctions"]
),

.library(
name: "JExtractSwiftLib",
targets: ["JExtractSwiftLib"]
Expand Down Expand Up @@ -214,6 +219,7 @@ let package = Package(
dependencies: [
"SwiftJava",
"SwiftJavaRuntimeSupport",
"SwiftRuntimeFunctions",
]
),

Expand Down Expand Up @@ -362,6 +368,18 @@ let package = Package(
]
),

.target(
name: "SwiftRuntimeFunctions",
dependencies: [
"CSwiftJavaJNI",
"SwiftJava"
],
swiftSettings: [
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),

.target(
name: "CSwiftJavaJNI",
swiftSettings: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public func asyncOptional(i: Int64) async throws -> Int64? {
return i
}

public func asyncThrows() async throws {
throw MySwiftError.swiftError
}

public func asyncRunningSum() async -> Int64 {
let totalSum = await withTaskGroup(of: Int64.self) { group in
// 1. Add child tasks to the group
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,22 @@ void asyncCopy() {
assertEquals(5, result.getY());
}
}

@Test
void asyncThrows() {
CompletableFuture<Void> future = MySwiftLibrary.asyncThrows();

ExecutionException ex = assertThrows(ExecutionException.class, future::get);

Throwable cause = ex.getCause();
assertNotNull(cause);
assertEquals(Exception.class, cause.getClass());
assertEquals("swiftError", cause.getMessage());
}

@Test
void asyncOptional() {
CompletableFuture<OptionalLong> future = MySwiftLibrary.asyncOptional(42);
assertEquals(OptionalLong.of(42), future.join());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ extension FFMSwift2JavaGenerator {
"""
// Generated by swift-java

import SwiftJavaRuntimeSupport
import SwiftRuntimeFunctions

""")

Expand Down Expand Up @@ -136,7 +136,7 @@ extension FFMSwift2JavaGenerator {
"""
// Generated by swift-java

import SwiftJavaRuntimeSupport
import SwiftRuntimeFunctions

"""
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ extension JNISwift2JavaGenerator {
nativeFunctionSignature.result.conversion,
swiftFunctionResultType: originalFunctionSignature.result.type,
nativeReturnType: nativeFunctionSignature.result.javaType,
outParameters: nativeFunctionSignature.result.outParameters
outParameters: nativeFunctionSignature.result.outParameters,
isThrowing: originalFunctionSignature.isThrowing
)
nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,8 @@ extension JNISwift2JavaGenerator {
NativeSwiftConversionStep,
swiftFunctionResultType: SwiftType,
nativeReturnType: JavaType,
outParameters: [JavaParameter]
outParameters: [JavaParameter],
isThrowing: Bool
)

/// Returns the conversion string applied to the placeholder.
Expand Down Expand Up @@ -808,7 +809,8 @@ extension JNISwift2JavaGenerator {
let inner,
let swiftFunctionResultType,
let nativeReturnType,
let outParameters
let outParameters,
let isThrowing
):
// Global ref all indirect returns
for outParameter in outParameters {
Expand All @@ -818,71 +820,66 @@ extension JNISwift2JavaGenerator {
printer.print(
"""
let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
let completableFutureClazz$ = environment.interface.FindClass(environment, "java/util/concurrent/CompletableFuture")!
let completeMethodID = environment.interface.GetMethodID(environment, completableFutureClazz$, "complete", "(Ljava/lang/Object;)Z")!
"""
)

printer.printBraceBlock("Task") { printer in
func printDo(printer: inout CodePrinter) {
printer.print("let swiftResult$ = await \(placeholder)")
printer.print(
"""
let environment = try JavaVirtualMachine.shared().environment()
"""
)
printer.printBraceBlock("defer") { printer in
printer.print("environment.interface.DeleteGlobalRef(environment, globalFuture)")
for outParameter in outParameters {
printer.print("environment.interface.DeleteGlobalRef(environment, \(outParameter.name))")
}
}
printer.print("environment = try JavaVirtualMachine.shared().environment()")
let inner = inner.render(&printer, "swiftResult$")
if swiftFunctionResultType.isVoid {
printer.print(inner)
printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])")
} else {
printer.printBraceBlock("withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)])") { printer in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Android, I'm getting this compilation error:

withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) {
    |                                                                  `- error: cannot convert value of type 'jobject' (aka 'UnsafeMutableRawPointer') to expected element type 'any CVarArg'

This appears to originate from this code, though it compiles fine on Darwin. I'm not certain if this is an issue with my local Android sdk or not so just wanted to flag it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xZaph thanks for reporting this! seems like the issue is that the Android JNI imports jobjects as UnsafeRawMutablePointer, but that does not conform to CVarArg. And the "normal" JNI, imports it as OpaquePointer, which does.

I'll move away from withVaList, which also should fix swiftlang/swift-android-examples#15

printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, completeMethodID, $0)")
printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0)")
}
}
}

return ""
func printTask(printer: inout CodePrinter) {
printer.printBraceBlock("defer") { printer in
// Defer might on any thread, so we need to attach environment.
printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()")
printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)")
for outParameter in outParameters {
printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(outParameter.name))")
}
}
if isThrowing {
printer.printBraceBlock("do") { printer in
printDo(printer: &printer)
}
printer.printBraceBlock("catch") { printer in
// We might not be on the same thread after the suspension, so we need to attach the thread again.
printer.print(
"""
let catchEnvironment = try! JavaVirtualMachine.shared().environment()
let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)])
catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)])
"""
)
}
} else {
printDo(printer: &printer)
}
}

// printer.print("let _semaphore$ = _Semaphore(value: 0)")
// let resultType = isThrowing ? "Result<\(swiftFunctionResultType), any Error>" : swiftFunctionResultType.description
// printer.print("var swiftResult$: \(resultType)!")
//
// func printInner(printer: inout CodePrinter) {
// if isThrowing {
// printer.printBraceBlock("do") { printer in
// printer.print("swiftResult$ = await Result.success(\(placeholder))")
// }
// printer.printBraceBlock("catch") { printer in
// printer.print("swiftResult$ = Result.failure(error)")
// }
// } else {
// printer.print("swiftResult$ = await \(placeholder)")
// }
// printer.print("_semaphore$.signal()")
// }
//
// printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in
// printer.printBraceBlock("Task.immediate") { printer in
// printInner(printer: &printer)
// }
// }
// printer.printBraceBlock("else") { printer in
// printer.printBraceBlock("Task") { printer in
// printInner(printer: &printer)
// }
// }
// printer.print(
// """
// _semaphore$.wait()
// """
// )
// let inner = inner.render(&printer, isThrowing ? "try swiftResult$.get()" : "swiftResult$")
// return inner
printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in
printer.printBraceBlock("Task.immediate") { printer in
// Immediate runs on the caller thread, so we don't need to attach the environment again.
printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it.
printTask(printer: &printer)
}
}
printer.printBraceBlock("else") { printer in
printer.printBraceBlock("Task") { printer in
// We can be on any thread, so we need to attach the thread.
printer.print("var environment = try! JavaVirtualMachine.shared().environment()")
printTask(printer: &printer)
}
}

return ""
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ extension JNISwift2JavaGenerator {
}
}

if decl.isThrowing {
if decl.isThrowing, !decl.isAsync {
let dummyReturn: String

if nativeSignature.result.javaType.isVoid {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public struct SwiftFunctionSignature: Equatable {
effectSpecifiers.contains(.async)
}

var isThrowing: Bool {
effectSpecifiers.contains(.throws)
}

init(
selfParameter: SwiftSelfParameter? = nil,
parameters: [SwiftParameter],
Expand Down
53 changes: 53 additions & 0 deletions Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import SwiftJava

extension _JNIMethodIDCache {
public enum CompletableFuture {
private static let completeMethod = Method(
name: "complete",
signature: "(Ljava/lang/Object;)Z"
)

private static let completeExceptionallyMethod = Method(
name: "completeExceptionally",
signature: "(Ljava/lang/Throwable;)Z"
)

private static let cache = _JNIMethodIDCache(
environment: try! JavaVirtualMachine.shared().environment(),
className: "java/util/concurrent/CompletableFuture",
methods: [completeMethod, completeExceptionallyMethod]
)

public static var `class`: jclass {
cache.javaClass
}

/// CompletableFuture<T>.complete(T)
public static var complete: jmethodID {
cache.methods[completeMethod]!
}

/// CompletableFuture<T>.completeExceptionally(Throwable)
public static var completeExceptionally: jmethodID {
cache.methods[completeExceptionallyMethod]!
}
}

public enum Exception {
private static let messageConstructor = Method(name: "<init>", signature: "(Ljava/lang/String;)V")

private static let cache = _JNIMethodIDCache(
environment: try! JavaVirtualMachine.shared().environment(),
className: "java/lang/Exception",
methods: [messageConstructor]
)

public static var `class`: jclass {
cache.javaClass
}

public static var constructWithMessage: jmethodID {
cache.methods[messageConstructor]!
}
}
}
76 changes: 0 additions & 76 deletions Sources/SwiftJavaRuntimeSupport/_Semaphore.swift

This file was deleted.

Loading