Skip to content

Commit 7c14ea0

Browse files
authored
Fix async Rust fn return Result<(), OpaqueType> (#221)
Fixes #220 This supports bridges of the form: ```rust #[swift_bridge::bridge] mod ffi { extern "Rust" { type ErrorType; async fn some_function() -> Result<(), ErrorType>; } } async fn some_function() -> Result<(), ErrorType> { Ok(()) } ```
1 parent de05902 commit 7c14ea0

File tree

4 files changed

+144
-3
lines changed

4 files changed

+144
-3
lines changed

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/AsyncTests.swift

+11
Original file line numberDiff line numberDiff line change
@@ -180,5 +180,16 @@ class AsyncTests: XCTestCase {
180180
func testSwiftCallsRustAsyncFnRetStruct() async throws {
181181
let _: AsyncRustFnReturnStruct = await rust_async_return_struct()
182182
}
183+
184+
func testSwiftCallsRustAsyncFnReturnResultNullOpaqueRust() async throws {
185+
try await rust_async_func_return_result_null_opaque_rust(true)
186+
187+
do {
188+
try await rust_async_func_return_result_null_opaque_rust(false)
189+
XCTFail()
190+
} catch let error as AsyncResultOpaqueRustType2 {
191+
XCTAssertEqual(error.val(), 111)
192+
}
193+
}
183194
}
184195

crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs

+20-3
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ impl BuiltInResult {
194194
self.custom_c_struct_name(types)
195195
);
196196
}
197+
if self.ok_ty.can_be_encoded_with_zero_bytes() {
198+
return "UnsafeMutableRawPointer?".to_string();
199+
}
197200
"__private__ResultPtrAndPtr".to_string()
198201
}
199202
}
@@ -477,11 +480,25 @@ typedef struct {c_enum_name}{{{c_tag_name} tag; union {c_fields_name} payload;}}
477480
let ok = self.ok_ty.to_swift_type(type_pos, types);
478481
let err = self.err_ty.to_swift_type(type_pos, types);
479482

483+
let (ok_val, err_val, condition) = if self.ok_ty.can_be_encoded_with_zero_bytes() {
484+
(
485+
ok,
486+
format!("{err}(ptr: rustFnRetVal!)"),
487+
"rustFnRetVal == nil",
488+
)
489+
} else {
490+
(
491+
format!("{ok}(ptr: rustFnRetVal.ok_or_err!)"),
492+
format!("{err}(ptr: rustFnRetVal.ok_or_err!)"),
493+
"rustFnRetVal.is_ok",
494+
)
495+
};
496+
480497
format!(
481-
r#"if rustFnRetVal.is_ok {{
482-
wrapper.cb(.success({ok}(ptr: rustFnRetVal.ok_or_err!)))
498+
r#"if {condition} {{
499+
wrapper.cb(.success({ok_val}))
483500
}} else {{
484-
wrapper.cb(.failure({err}(ptr: rustFnRetVal.ok_or_err!)))
501+
wrapper.cb(.failure({err_val}))
485502
}}"#
486503
)
487504
}

crates/swift-bridge-ir/src/codegen/codegen_tests/async_function_codegen_tests.rs

+100
Original file line numberDiff line numberDiff line change
@@ -949,3 +949,103 @@ void __swift_bridge__$some_function(void* callback_wrapper, void __swift_bridge_
949949
.test();
950950
}
951951
}
952+
953+
/// Verify that we generate the correct code for extern "Rust" async functions that returns a Result<(), OpaqueRustType>.
954+
mod extern_rust_async_function_returns_result_null_opaque {
955+
use super::*;
956+
957+
fn bridge_module() -> TokenStream {
958+
quote! {
959+
#[swift_bridge::bridge]
960+
mod ffi {
961+
extern "Rust" {
962+
type ErrorType;
963+
async fn some_function() -> Result<(), ErrorType>;
964+
}
965+
}
966+
}
967+
}
968+
969+
fn expected_rust_tokens() -> ExpectedRustTokens {
970+
ExpectedRustTokens::Contains(quote! {
971+
pub extern "C" fn __swift_bridge__some_function(
972+
callback_wrapper: *mut std::ffi::c_void,
973+
callback: extern "C" fn(*mut std::ffi::c_void, *mut super::ErrorType) -> (),
974+
) {
975+
let callback_wrapper = swift_bridge::async_support::SwiftCallbackWrapper(callback_wrapper);
976+
let fut = super::some_function();
977+
let task = async move {
978+
let val = match fut.await {
979+
Ok(ok) => std::ptr::null_mut(),
980+
Err(err) => Box::into_raw(Box::new({
981+
let val: super::ErrorType = err;
982+
val
983+
})) as *mut super::ErrorType
984+
};
985+
let callback_wrapper = callback_wrapper;
986+
let callback_wrapper = callback_wrapper.0;
987+
(callback)(callback_wrapper, val)
988+
};
989+
swift_bridge::async_support::ASYNC_RUNTIME.spawn_task(Box::pin(task))
990+
}
991+
})
992+
}
993+
994+
// TODO: Replace `Error` with the concrete error type `ErrorType`.
995+
// As of Feb 2023 using the concrete error type leads to a compile time error.
996+
// This seems like a bug in the Swift compiler.
997+
998+
fn expected_swift_code() -> ExpectedSwiftCode {
999+
ExpectedSwiftCode::ContainsAfterTrim(
1000+
r#"
1001+
public func some_function() async throws -> () {
1002+
func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?, rustFnRetVal: UnsafeMutableRawPointer?) {
1003+
let wrapper = Unmanaged<CbWrapper$some_function>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
1004+
if rustFnRetVal == nil {
1005+
wrapper.cb(.success(()))
1006+
} else {
1007+
wrapper.cb(.failure(ErrorType(ptr: rustFnRetVal!)))
1008+
}
1009+
}
1010+
1011+
return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<(), Error>) in
1012+
let callback = { rustFnRetVal in
1013+
continuation.resume(with: rustFnRetVal)
1014+
}
1015+
1016+
let wrapper = CbWrapper$some_function(cb: callback)
1017+
let wrapperPtr = Unmanaged.passRetained(wrapper).toOpaque()
1018+
1019+
__swift_bridge__$some_function(wrapperPtr, onComplete)
1020+
})
1021+
}
1022+
class CbWrapper$some_function {
1023+
var cb: (Result<(), Error>) -> ()
1024+
1025+
public init(cb: @escaping (Result<(), Error>) -> ()) {
1026+
self.cb = cb
1027+
}
1028+
}
1029+
"#,
1030+
)
1031+
}
1032+
1033+
fn expected_c_header() -> ExpectedCHeader {
1034+
ExpectedCHeader::ContainsAfterTrim(
1035+
r#"
1036+
void __swift_bridge__$some_function(void* callback_wrapper, void __swift_bridge__$some_function$async(void* callback_wrapper, void* ret));
1037+
"#,
1038+
)
1039+
}
1040+
1041+
#[test]
1042+
fn extern_rust_async_function_returns_result_null_opaque() {
1043+
CodegenTest {
1044+
bridge_module: bridge_module().into(),
1045+
expected_rust_tokens: expected_rust_tokens(),
1046+
expected_swift_code: expected_swift_code(),
1047+
expected_c_header: expected_c_header(),
1048+
}
1049+
.test();
1050+
}
1051+
}

crates/swift-integration-tests/src/async_function.rs

+13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ mod ffi {
1313
async fn rust_async_func_reflect_result_opaque_rust(
1414
arg: Result<AsyncResultOpaqueRustType1, AsyncResultOpaqueRustType2>,
1515
) -> Result<AsyncResultOpaqueRustType1, AsyncResultOpaqueRustType2>;
16+
async fn rust_async_func_return_result_null_opaque_rust(
17+
succeed: bool,
18+
) -> Result<(), AsyncResultOpaqueRustType2>;
1619
}
1720

1821
extern "Rust" {
@@ -176,3 +179,13 @@ async fn rust_async_func_return_result_null_and_transparent_enum(
176179
))
177180
}
178181
}
182+
183+
async fn rust_async_func_return_result_null_opaque_rust(
184+
succeed: bool,
185+
) -> Result<(), AsyncResultOpaqueRustType2> {
186+
if succeed {
187+
Ok(())
188+
} else {
189+
Err(AsyncResultOpaqueRustType2(111))
190+
}
191+
}

0 commit comments

Comments
 (0)