From c45a38cf8730013a6a03af2184df0831f523b1a6 Mon Sep 17 00:00:00 2001 From: Britt Date: Mon, 14 Oct 2024 16:20:31 -0400 Subject: [PATCH] Support Rust returning `-> Result<_, String>` (#296) Support Rust returning `-> Result<_, String>`, e.g.: ```rust #[swift_bridge::bridge] mod ffi { extern "Rust" { fn do_fallible_work() -> Result<(), String>; } } ``` --- Incorporate the changes from #282 to implement swift's `Error` protocol for ruststring, & extend them with the tests requested in https://github.com/chinedufn/swift-bridge/issues/295#issuecomment-2411049833 --- .../ResultTests.swift | 40 +++++++--- .../src/generate_core/rust_string.swift | 5 +- .../codegen_tests/result_codegen_tests.rs | 76 +++++++++++++++++-- crates/swift-integration-tests/src/result.rs | 9 +++ 4 files changed, 112 insertions(+), 18 deletions(-) diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift index 058ec2e1..5528acd0 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift @@ -13,14 +13,30 @@ class ResultTests: XCTestCase { rust_func_takes_result_string(.Ok("Success Message")) rust_func_takes_result_string(.Err("Error Message")) } - + + /// Verify that we can return a Result from Rust -> Swift. + /// + /// The Err case evidences Swift’s `Error` protocol is implemented correctly + /// for `RustStringRef`, i.e. `extension RustStringRef: Error {}` + func testSwiftCallRustReturnsResultString() throws { + let resultOk = try! rust_func_returns_result_string(true) + XCTAssertEqual(resultOk.toString(), "Success Message") + + do { + let _ = try rust_func_returns_result_string(false) + XCTFail("The function should have returned an error.") + } catch let error as RustString { + XCTAssertEqual(error.toString(), "Error Message") + } + } + /// Verify that we can pass a Result from Swift -> Rust func testSwiftCallRustResultOpaqueRust() throws { let reflectedOk = try! rust_func_reflect_result_opaque_rust( .Ok(ResultTestOpaqueRustType(111)) ) XCTAssertEqual(reflectedOk.val(), 111) - + do { let _ = try rust_func_reflect_result_opaque_rust( .Err(ResultTestOpaqueRustType(222)) @@ -30,7 +46,7 @@ class ResultTests: XCTestCase { XCTAssertEqual(error.val(), 222) } } - + /// Verify that we can pass a Result from Swift -> Rust func testSwiftCallRustResultOpaqueSwift() throws { rust_func_takes_result_opaque_swift( @@ -64,7 +80,7 @@ class ResultTests: XCTestCase { XCTAssertEqual(error.val(), 222) } } - + /// Verify that we can receive a Result from Rust func testResultOpaqueRustTransparentEnum() throws { XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") { @@ -75,7 +91,7 @@ class ResultTests: XCTestCase { XCTFail() } } - + XCTContext.runActivity(named: "Should throw an error") { _ in do { @@ -95,7 +111,7 @@ class ResultTests: XCTestCase { } } } - + /// Verify that we can receive a Result from Rust func testResultTransparentEnumOpaqueRust() throws { XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") { @@ -114,7 +130,7 @@ class ResultTests: XCTestCase { XCTFail() } } - + XCTContext.runActivity(named: "Should throw an error") { _ in do { @@ -127,7 +143,7 @@ class ResultTests: XCTestCase { } } } - + /// Verify that we can receive a Result<(), TransparentEnum> from Rust func testResultUnitTypeTransparentEnum() throws { XCTContext.runActivity(named: "Should return a Unit type") { @@ -138,7 +154,7 @@ class ResultTests: XCTestCase { XCTFail() } } - + XCTContext.runActivity(named: "Should throw an error") { _ in do { @@ -158,7 +174,7 @@ class ResultTests: XCTestCase { } } } - + /// Verify that we can receive a Result<(primitive type, OpaqueRustType, String), TransparentEnum> from Rust func testResultTupleTransparentEnum() throws { XCTContext.runActivity(named: "Should return a tuple type") { @@ -172,7 +188,7 @@ class ResultTests: XCTestCase { XCTFail() } } - + XCTContext.runActivity(named: "Should throw an error") { _ in do { @@ -249,7 +265,7 @@ class ResultTests: XCTestCase { XCTAssertEqual(UInt32(i), value.val()) } } - + /// Verify that we can use throwing initializers defined on the Rust side. func testThrowingInitializers() throws { XCTContext.runActivity(named: "Should fail") { diff --git a/crates/swift-bridge-build/src/generate_core/rust_string.swift b/crates/swift-bridge-build/src/generate_core/rust_string.swift index bc05257c..11d8a55e 100644 --- a/crates/swift-bridge-build/src/generate_core/rust_string.swift +++ b/crates/swift-bridge-build/src/generate_core/rust_string.swift @@ -47,6 +47,9 @@ extension RustStringRef { __swift_bridge__$RustString$trim(ptr) } } +/// exercised in SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift: +/// - see `func testSwiftCallRustReturnsResultString` +extension RustStringRef: Error {} extension RustString: Vectorizable { public static func vecOfSelfNew() -> UnsafeMutableRawPointer { __swift_bridge__$Vec_RustString$new() @@ -94,4 +97,4 @@ extension RustString: Vectorizable { public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt { __swift_bridge__$Vec_RustString$len(vecPtr) } -} \ No newline at end of file +} diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs index 35df26b6..2809697e 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs @@ -4,7 +4,7 @@ use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode} use proc_macro2::TokenStream; use quote::quote; -/// Test code generation for Rust function that accepts and returns a Result +/// Test code generation for Rust function that accepts a Result /// where T and E are Strings. mod extern_rust_fn_result_string { use super::*; @@ -64,6 +64,72 @@ void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg); } } +/// Test code generation for Rust function that returns a Result +/// where T and E are Strings. +mod extern_rust_fn_return_result_string { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + extern "Rust" { + fn some_function() -> Result; + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + #[export_name = "__swift_bridge__$some_function"] + pub extern "C" fn __swift_bridge__some_function( + ) -> swift_bridge::result::ResultPtrAndPtr { + match super::some_function() { + Ok(ok) => { + swift_bridge::result::ResultPtrAndPtr { + is_ok: true, + ok_or_err: swift_bridge::string::RustString(ok).box_into_raw() as *mut std::ffi::c_void + } + } + Err(err) => { + swift_bridge::result::ResultPtrAndPtr { + is_ok: false, + ok_or_err: swift_bridge::string::RustString(err).box_into_raw() as *mut std::ffi::c_void + } + } + } + } + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +public func some_function() throws -> RustString { + try { let val = __swift_bridge__$some_function(); if val.is_ok { return RustString(ptr: val.ok_or_err!) } else { throw RustString(ptr: val.ok_or_err!) } }() +} +"#, + ) + } + + const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim( + r#" +struct __private__ResultPtrAndPtr __swift_bridge__$some_function(void); + "#, + ); + + #[test] + fn extern_rust_fn_return_result_string() { + CodegenTest { + bridge_module: bridge_module_tokens().into(), + expected_rust_tokens: expected_rust_tokens(), + expected_swift_code: expected_swift_code(), + expected_c_header: EXPECTED_C_HEADER, + } + .test(); + } +} + /// Test code generation for Rust function that accepts a Result where T and E are /// opaque Rust types. mod extern_rust_fn_arg_result_opaque_rust { @@ -449,7 +515,7 @@ public func some_function() throws -> SomeOkType { r#" typedef enum __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag {__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultOk, __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultErr} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag; union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields {void* ok; struct __swift_bridge__$SomeErrEnum err;}; -typedef struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum{__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum; +typedef struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum{__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum; "#, r#"struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum __swift_bridge__$some_function(void)"#, ]) @@ -531,7 +597,7 @@ public func some_function() throws -> SomeOkEnum { r#" typedef enum __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag {__swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk, __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr} __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields {struct __swift_bridge__$SomeOkEnum ok; void* err;}; -typedef struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType{__swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields payload;} __swift_bridge__$ResultSomeOkEnumAndSomeErrType; +typedef struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType{__swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields payload;} __swift_bridge__$ResultSomeOkEnumAndSomeErrType; "#, r#"struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType __swift_bridge__$some_function(void)"#, ]) @@ -606,7 +672,7 @@ public func some_function() throws -> () { r#" typedef enum __swift_bridge__$ResultVoidAndSomeErrEnum$Tag {__swift_bridge__$ResultVoidAndSomeErrEnum$ResultOk, __swift_bridge__$ResultVoidAndSomeErrEnum$ResultErr} __swift_bridge__$ResultVoidAndSomeErrEnum$Tag; union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields {struct __swift_bridge__$SomeErrEnum err;}; -typedef struct __swift_bridge__$ResultVoidAndSomeErrEnum{__swift_bridge__$ResultVoidAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrEnum; +typedef struct __swift_bridge__$ResultVoidAndSomeErrEnum{__swift_bridge__$ResultVoidAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrEnum; "#, r#"struct __swift_bridge__$ResultVoidAndSomeErrEnum __swift_bridge__$some_function(void)"#, ]) @@ -689,7 +755,7 @@ public func some_function() throws -> (Int32, UInt32) { r#" typedef enum __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag {__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultOk, __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultErr} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag; union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields {struct __swift_bridge__$tuple$I32U32 ok; struct __swift_bridge__$SomeErrEnum err;}; -typedef struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum{__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag tag; union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields payload;} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum; +typedef struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum{__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag tag; union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields payload;} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum; "#, r#"struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum __swift_bridge__$some_function(void)"#, r#"typedef struct __swift_bridge__$tuple$I32U32 { int32_t _0; uint32_t _1; } __swift_bridge__$tuple$I32U32;"#, diff --git a/crates/swift-integration-tests/src/result.rs b/crates/swift-integration-tests/src/result.rs index a905a048..c1f45359 100644 --- a/crates/swift-integration-tests/src/result.rs +++ b/crates/swift-integration-tests/src/result.rs @@ -10,6 +10,8 @@ mod ffi { ) -> Result; fn rust_func_takes_result_string(arg: Result); + fn rust_func_returns_result_string(ok: bool) -> Result; + fn rust_func_takes_result_opaque_swift( arg: Result, ); @@ -109,6 +111,13 @@ fn rust_func_takes_result_string(arg: Result) { } } +fn rust_func_returns_result_string(ok: bool) -> Result { + if !ok { + return Err("Error Message".to_string()); + } + Ok("Success Message".to_string()) +} + fn rust_func_reflect_result_opaque_rust( arg: Result, ) -> Result {