Skip to content

Commit

Permalink
Fix async Rust fn return Result<(), OpaqueType> (#221)
Browse files Browse the repository at this point in the history
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(())
}
```
  • Loading branch information
jfaust authored May 4, 2023
1 parent de05902 commit 7c14ea0
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,16 @@ class AsyncTests: XCTestCase {
func testSwiftCallsRustAsyncFnRetStruct() async throws {
let _: AsyncRustFnReturnStruct = await rust_async_return_struct()
}

func testSwiftCallsRustAsyncFnReturnResultNullOpaqueRust() async throws {
try await rust_async_func_return_result_null_opaque_rust(true)

do {
try await rust_async_func_return_result_null_opaque_rust(false)
XCTFail()
} catch let error as AsyncResultOpaqueRustType2 {
XCTAssertEqual(error.val(), 111)
}
}
}

23 changes: 20 additions & 3 deletions crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ impl BuiltInResult {
self.custom_c_struct_name(types)
);
}
if self.ok_ty.can_be_encoded_with_zero_bytes() {
return "UnsafeMutableRawPointer?".to_string();
}
"__private__ResultPtrAndPtr".to_string()
}
}
Expand Down Expand Up @@ -477,11 +480,25 @@ typedef struct {c_enum_name}{{{c_tag_name} tag; union {c_fields_name} payload;}}
let ok = self.ok_ty.to_swift_type(type_pos, types);
let err = self.err_ty.to_swift_type(type_pos, types);

let (ok_val, err_val, condition) = if self.ok_ty.can_be_encoded_with_zero_bytes() {
(
ok,
format!("{err}(ptr: rustFnRetVal!)"),
"rustFnRetVal == nil",
)
} else {
(
format!("{ok}(ptr: rustFnRetVal.ok_or_err!)"),
format!("{err}(ptr: rustFnRetVal.ok_or_err!)"),
"rustFnRetVal.is_ok",
)
};

format!(
r#"if rustFnRetVal.is_ok {{
wrapper.cb(.success({ok}(ptr: rustFnRetVal.ok_or_err!)))
r#"if {condition} {{
wrapper.cb(.success({ok_val}))
}} else {{
wrapper.cb(.failure({err}(ptr: rustFnRetVal.ok_or_err!)))
wrapper.cb(.failure({err_val}))
}}"#
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -949,3 +949,103 @@ void __swift_bridge__$some_function(void* callback_wrapper, void __swift_bridge_
.test();
}
}

/// Verify that we generate the correct code for extern "Rust" async functions that returns a Result<(), OpaqueRustType>.
mod extern_rust_async_function_returns_result_null_opaque {
use super::*;

fn bridge_module() -> TokenStream {
quote! {
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
type ErrorType;
async fn some_function() -> Result<(), ErrorType>;
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
pub extern "C" fn __swift_bridge__some_function(
callback_wrapper: *mut std::ffi::c_void,
callback: extern "C" fn(*mut std::ffi::c_void, *mut super::ErrorType) -> (),
) {
let callback_wrapper = swift_bridge::async_support::SwiftCallbackWrapper(callback_wrapper);
let fut = super::some_function();
let task = async move {
let val = match fut.await {
Ok(ok) => std::ptr::null_mut(),
Err(err) => Box::into_raw(Box::new({
let val: super::ErrorType = err;
val
})) as *mut super::ErrorType
};
let callback_wrapper = callback_wrapper;
let callback_wrapper = callback_wrapper.0;
(callback)(callback_wrapper, val)
};
swift_bridge::async_support::ASYNC_RUNTIME.spawn_task(Box::pin(task))
}
})
}

// TODO: Replace `Error` with the concrete error type `ErrorType`.
// As of Feb 2023 using the concrete error type leads to a compile time error.
// This seems like a bug in the Swift compiler.

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() async throws -> () {
func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?, rustFnRetVal: UnsafeMutableRawPointer?) {
let wrapper = Unmanaged<CbWrapper$some_function>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
if rustFnRetVal == nil {
wrapper.cb(.success(()))
} else {
wrapper.cb(.failure(ErrorType(ptr: rustFnRetVal!)))
}
}
return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<(), Error>) in
let callback = { rustFnRetVal in
continuation.resume(with: rustFnRetVal)
}
let wrapper = CbWrapper$some_function(cb: callback)
let wrapperPtr = Unmanaged.passRetained(wrapper).toOpaque()
__swift_bridge__$some_function(wrapperPtr, onComplete)
})
}
class CbWrapper$some_function {
var cb: (Result<(), Error>) -> ()
public init(cb: @escaping (Result<(), Error>) -> ()) {
self.cb = cb
}
}
"#,
)
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsAfterTrim(
r#"
void __swift_bridge__$some_function(void* callback_wrapper, void __swift_bridge__$some_function$async(void* callback_wrapper, void* ret));
"#,
)
}

#[test]
fn extern_rust_async_function_returns_result_null_opaque() {
CodegenTest {
bridge_module: bridge_module().into(),
expected_rust_tokens: expected_rust_tokens(),
expected_swift_code: expected_swift_code(),
expected_c_header: expected_c_header(),
}
.test();
}
}
13 changes: 13 additions & 0 deletions crates/swift-integration-tests/src/async_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ mod ffi {
async fn rust_async_func_reflect_result_opaque_rust(
arg: Result<AsyncResultOpaqueRustType1, AsyncResultOpaqueRustType2>,
) -> Result<AsyncResultOpaqueRustType1, AsyncResultOpaqueRustType2>;
async fn rust_async_func_return_result_null_opaque_rust(
succeed: bool,
) -> Result<(), AsyncResultOpaqueRustType2>;
}

extern "Rust" {
Expand Down Expand Up @@ -176,3 +179,13 @@ async fn rust_async_func_return_result_null_and_transparent_enum(
))
}
}

async fn rust_async_func_return_result_null_opaque_rust(
succeed: bool,
) -> Result<(), AsyncResultOpaqueRustType2> {
if succeed {
Ok(())
} else {
Err(AsyncResultOpaqueRustType2(111))
}
}

0 comments on commit 7c14ea0

Please sign in to comment.