diff --git a/README.md b/README.md index b773dd40..164b0132 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ In addition to allowing you to share your own custom structs, enums and classes | *const T | UnsafePointer\ | | | *mut T | UnsafeMutablePointer\ | | | Option\ | Optional\ | | -| Result\ | | Not yet implemented | +| Result\ | RustResult\ | | | Have a Rust standard library type in mind?
Open an issue! | | | | | Have a Swift standard library type in mind?
Open an issue! | | diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj index 82b47833..f68dc3df 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 221E16B42786233600F94AC0 /* ConditionalCompilationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */; }; 221E16B62786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */; }; 22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22553323281DB5FC008A3121 /* GenericTests.rs.swift */; }; + 225908FC28DA0E320080C737 /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225908FB28DA0E320080C737 /* ResultTests.swift */; }; + 225908FE28DA0F9F0080C737 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225908FD28DA0F9F0080C737 /* Result.swift */; }; 226F944B27BF79B400243D86 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226F944A27BF79B400243D86 /* String.swift */; }; 228FE5D52740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */; }; 228FE5D72740DB6A00805D9E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE5D62740DB6A00805D9E /* ContentView.swift */; }; @@ -72,6 +74,8 @@ 221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalCompilationTests.swift; sourceTree = ""; }; 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpaqueTypeAttributeTests.swift; sourceTree = ""; }; 22553323281DB5FC008A3121 /* GenericTests.rs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericTests.rs.swift; sourceTree = ""; }; + 225908FB28DA0E320080C737 /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; + 225908FD28DA0F9F0080C737 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 226F944A27BF79B400243D86 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 228FE5D12740DB6A00805D9E /* SwiftRustIntegrationTestRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftRustIntegrationTestRunner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftRustIntegrationTestRunnerApp.swift; sourceTree = ""; }; @@ -166,6 +170,7 @@ 228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */, 22EE4E0828B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift */, 22C0625228CE699D007A6F67 /* Callbacks.swift */, + 225908FD28DA0F9F0080C737 /* Result.swift */, ); path = SwiftRustIntegrationTestRunner; sourceTree = ""; @@ -190,6 +195,7 @@ 228FE61127428A8D00805D9E /* OpaqueSwiftStructTests.swift */, 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */, 22043294274ADA7A00BAE645 /* OptionTests.swift */, + 225908FB28DA0E320080C737 /* ResultTests.swift */, 220432A6274C953E00BAE645 /* PointerTests.swift */, 220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */, 2202BC0727B2DD1700D43CC4 /* SharedEnumTests.swift */, @@ -363,6 +369,7 @@ 220432EA2753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift in Sources */, 22FD1C542753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift in Sources */, 220432A9274D31DC00BAE645 /* Pointer.swift in Sources */, + 225908FE28DA0F9F0080C737 /* Result.swift in Sources */, 228FE5D72740DB6A00805D9E /* ContentView.swift in Sources */, 228FE64E2749C3D700805D9E /* Boolean.swift in Sources */, 228FE64627480E1D00805D9E /* SwiftBridgeCore.swift in Sources */, @@ -385,6 +392,7 @@ 220432AF274E7BF800BAE645 /* SharedStructTests.swift in Sources */, 220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */, 22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */, + 225908FC28DA0E320080C737 /* ResultTests.swift in Sources */, 228FE6502749C43100805D9E /* BooleanTests.swift in Sources */, 2202BC0827B2DD1700D43CC4 /* SharedEnumTests.swift in Sources */, 22BCAAB927A2607700686A21 /* FunctionAttributeIdentifiableTests.swift in Sources */, diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/Result.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/Result.swift new file mode 100644 index 00000000..dacecc0f --- /dev/null +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/Result.swift @@ -0,0 +1,12 @@ +// +// Result.swift +// SwiftRustIntegrationTestRunner +// +// Created by Frankie Nwafili on 9/20/22. +// + +func swift_func_takes_callback_with_result_arg( + arg: (RustResult) -> () +) { + arg(.Ok(CallbackTestOpaqueRustType(555))) +} diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift new file mode 100644 index 00000000..74fc4909 --- /dev/null +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift @@ -0,0 +1,27 @@ +// +// ResultTests.swift +// SwiftRustIntegrationTestRunnerTests +// +// Created by Frankie Nwafili on 9/20/22. +// + +import XCTest +@testable import SwiftRustIntegrationTestRunner + +class ResultTests: XCTestCase { + /// Verify that we can pass a Result from Swift -> Rust + func testSwiftCallRustResultString() throws { + rust_func_takes_result_string(.Ok("Success Message")) + rust_func_takes_result_string(.Err("Error Message")) + } + + /// Verify that we can pass a Result from Swift -> Rust + func testSwiftCallRustResultOpaqueRust() throws { + rust_func_takes_result_opaque_rust( + .Ok(ResultTestOpaqueRustType(111)) + ) + rust_func_takes_result_opaque_rust( + .Err(ResultTestOpaqueRustType(222)) + ) + } +} diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index b52f79a3..05ed5a79 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -20,10 +20,11 @@ - [Conditional Compilation](./bridge-module/conditional-compilation/README.md) - [Built In Types](./built-in/README.md) - - [Option <---> Optional](./built-in/option/README.md) - - [Vec <---> RustVec](./built-in/vec/README.md) - [String <---> String](./built-in/string/README.md) - [&str <---> RustStr](./built-in/str/README.md) + - [Vec <---> RustVec](./built-in/vec/README.md) + - [Option <---> Optional](./built-in/option/README.md) + - [Result <---> RustResult](./built-in/result/README.md) - [Box C>](./built-in/boxed-functions/README.md) - [Safety](./safety/README.md) diff --git a/book/src/built-in/result/README.md b/book/src/built-in/result/README.md new file mode 100644 index 00000000..bbff3260 --- /dev/null +++ b/book/src/built-in/result/README.md @@ -0,0 +1,27 @@ +# Result + +Rust's `Result` is seen on the Swift side as a `RustResult`. + +## Example + +```rust,no_run +// Rust + +#[swift_bridge::bridge] +mod ffi { + extern "Swift" { + fn run( + arg: Box)> + ); + } + + extern "Rust" { + type SomeRustType; + } +``` + +```swift +func run(arg: (RustResult) -> ()) { + arg(.Err("Something went wrong")) +} +``` diff --git a/crates/swift-bridge-build/src/generate_core.rs b/crates/swift-bridge-build/src/generate_core.rs index c54cc81b..7801288a 100644 --- a/crates/swift-bridge-build/src/generate_core.rs +++ b/crates/swift-bridge-build/src/generate_core.rs @@ -1,6 +1,7 @@ use crate::generate_core::boxed_fn_support::{ C_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN, SWIFT_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN, }; +use crate::generate_core::result_support::{C_RESULT_SUPPORT, SWIFT_RUST_RESULT}; use std::path::{Path, PathBuf}; const RUST_STRING_SWIFT: &'static str = include_str!("./generate_core/rust_string.swift"); @@ -10,6 +11,7 @@ const STRING_SWIFT: &'static str = include_str!("./generate_core/string.swift"); const RUST_VEC_SWIFT: &'static str = include_str!("./generate_core/rust_vec.swift"); mod boxed_fn_support; +mod result_support; pub(super) fn write_core_swift_and_c(out_dir: &Path) { let core_swift_out = out_dir.join("SwiftBridgeCore.swift"); @@ -18,6 +20,8 @@ pub(super) fn write_core_swift_and_c(out_dir: &Path) { swift += &RUST_STRING_SWIFT; swift += "\n"; swift += &SWIFT_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN; + swift += "\n"; + swift += &SWIFT_RUST_RESULT; std::fs::write(core_swift_out, swift).unwrap(); @@ -27,6 +31,8 @@ pub(super) fn write_core_swift_and_c(out_dir: &Path) { c_header += &RUST_STRING_C; c_header += "\n"; c_header += &C_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN; + c_header += "\n"; + c_header += &C_RESULT_SUPPORT; std::fs::write(core_c_header_out, c_header).unwrap(); } diff --git a/crates/swift-bridge-build/src/generate_core/result_support.rs b/crates/swift-bridge-build/src/generate_core/result_support.rs new file mode 100644 index 00000000..3aefc00c --- /dev/null +++ b/crates/swift-bridge-build/src/generate_core/result_support.rs @@ -0,0 +1,40 @@ +pub const SWIFT_RUST_RESULT: &'static str = r#" +public enum RustResult { + case Ok(T) + case Err(E) +} + +extension RustResult { + func ok() -> T? { + switch self { + case .Ok(let ok): + return ok + case .Err(_): + return nil + } + } + + func err() -> E? { + switch self { + case .Ok(_): + return nil + case .Err(let err): + return err + } + } + + func toResult() -> Result + where E: Error { + switch self { + case .Ok(let ok): + return .success(ok) + case .Err(let err): + return .failure(err) + } + } +} +"#; + +pub const C_RESULT_SUPPORT: &'static str = r#" +struct __private__ResultPtrAndPtr { bool is_ok; void* ok_or_err; }; +"#; diff --git a/crates/swift-bridge-ir/src/bridged_type.rs b/crates/swift-bridge-ir/src/bridged_type.rs index 24ae8de2..de0d4650 100644 --- a/crates/swift-bridge-ir/src/bridged_type.rs +++ b/crates/swift-bridge-ir/src/bridged_type.rs @@ -3,6 +3,7 @@ use std::ops::Deref; use std::str::FromStr; use crate::bridged_type::boxed_fn::BuiltInBoxedFnOnce; +use crate::bridged_type::bridged_result::BuiltInResult; use proc_macro2::{Ident, Span, TokenStream}; use quote::ToTokens; use quote::{quote, quote_spanned}; @@ -17,6 +18,7 @@ pub(crate) use self::shared_struct::{SharedStruct, StructFields, StructSwiftRepr pub(crate) mod boxed_fn; mod bridged_option; +mod bridged_result; mod shared_enum; pub(crate) mod shared_struct; @@ -62,6 +64,7 @@ pub(crate) enum StdLibType { Vec(BuiltInVec), BoxedFnOnce(BuiltInBoxedFnOnce), Option(BridgedOption), + Result(BuiltInResult), } /// TODO: Add this to `OpaqueForeignType` @@ -405,6 +408,10 @@ impl BridgedType { return Some(BridgedType::StdLib(StdLibType::Option(BridgedOption { ty: Box::new(inner), }))); + } else if string.starts_with("Result < ") { + return Some(BridgedType::StdLib(StdLibType::Result( + BuiltInResult::from_str_tokens(&string, types)?, + ))); } else if string.starts_with("Box < dyn FnOnce") { return Some(BridgedType::StdLib(StdLibType::BoxedFnOnce( BuiltInBoxedFnOnce::from_str_tokens(&string, types)?, @@ -426,6 +433,7 @@ impl BridgedType { "f64" => BridgedType::StdLib(StdLibType::F64), "String" => BridgedType::StdLib(StdLibType::String), "bool" => BridgedType::StdLib(StdLibType::Bool), + "()" => BridgedType::StdLib(StdLibType::Null), _ => { let bridged_type = types.get(string)?; let bridged_type = bridged_type.to_bridged_type(false, false); @@ -487,6 +495,7 @@ impl BridgedType { let ty = opt.ty.to_rust_type_path(); quote! { Option<#ty> } } + StdLibType::Result(result) => result.to_rust_type_path(), StdLibType::BoxedFnOnce(fn_once) => fn_once.to_rust_type_path(), } } @@ -636,6 +645,9 @@ impl BridgedType { StdLibType::Option(_) => { todo!("Option> is not yet supported") } + StdLibType::Result(_) => { + todo!("Option> is not yet supported") + } StdLibType::BoxedFnOnce(_) => { todo!("Support Box C>") } @@ -666,6 +678,7 @@ impl BridgedType { } } }, + StdLibType::Result(result) => result.to_ffi_compatible_rust_type(swift_bridge_path), StdLibType::BoxedFnOnce(fn_once) => fn_once.to_ffi_compatible_rust_type(), }, BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(shared_struct))) => { @@ -823,6 +836,7 @@ impl BridgedType { unimplemented!() } }, + StdLibType::Result(result) => result.to_swift_type(type_pos, types), StdLibType::BoxedFnOnce(boxed_fn) => boxed_fn.to_swift_type().to_string(), }, BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(shared_struct))) => { @@ -952,6 +966,7 @@ impl BridgedType { StdLibType::String => "void*".to_string(), StdLibType::Vec(_) => "void*".to_string(), StdLibType::Option(opt) => opt.to_c(), + StdLibType::Result(result) => result.to_c().to_string(), StdLibType::BoxedFnOnce(_) => "void*".to_string(), }, BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(shared_struct))) => { @@ -1073,6 +1088,9 @@ impl BridgedType { StdLibType::Option(opt) => { opt.convert_rust_value_to_ffi_value(expression, swift_bridge_path) } + StdLibType::Result(_) => { + todo!("Result is not yet supported") + } StdLibType::BoxedFnOnce(fn_once) => { fn_once.convert_rust_value_to_ffi_compatible_value(expression) } @@ -1129,7 +1147,13 @@ impl BridgedType { // RustStr -> &str // *mut RustString -> String // FfiSlice -> &[u8] - pub fn convert_ffi_value_to_rust_value(&self, value: &TokenStream, span: Span) -> TokenStream { + pub fn convert_ffi_value_to_rust_value( + &self, + value: &TokenStream, + span: Span, + swift_bridge_path: &Path, + types: &TypeDeclarations, + ) -> TokenStream { match self { BridgedType::StdLib(stdlib_type) => match stdlib_type { StdLibType::Null @@ -1170,6 +1194,9 @@ impl BridgedType { StdLibType::Option(bridged_option) => { bridged_option.convert_ffi_value_to_rust_value(value) } + StdLibType::Result(result) => { + result.convert_ffi_value_to_rust_value(value, span, swift_bridge_path, types) + } StdLibType::BoxedFnOnce(_) => { todo!("Support Box C>") } @@ -1305,6 +1332,9 @@ impl BridgedType { format!("RustVec(ptr: {})", value) } StdLibType::Option(opt) => opt.convert_ffi_expression_to_swift(value), + StdLibType::Result(_) => { + todo!("Result is not yet supported") + } StdLibType::BoxedFnOnce(fn_once) => { fn_once.convert_ffi_value_to_swift_value(type_pos) } @@ -1427,6 +1457,9 @@ impl BridgedType { StdLibType::Option(option) => { option.convert_swift_expression_to_ffi_compatible(value, type_pos) } + StdLibType::Result(result) => { + result.convert_swift_expression_to_ffi_compatible(value, type_pos) + } StdLibType::BoxedFnOnce(_) => { todo!("Support Box C>") } @@ -1602,6 +1635,9 @@ impl BridgedType { StdLibType::Option(_) => { todo!("Support nested Option>") } + StdLibType::Result(_) => { + todo!("Result is not yet supported") + } StdLibType::BoxedFnOnce(_) => { todo!("Support Box C>") } @@ -1643,6 +1679,10 @@ impl BridgedType { StdLibType::String => true, StdLibType::Vec(inner) => inner.ty.contains_owned_string_recursive(), StdLibType::Option(inner) => inner.ty.contains_owned_string_recursive(), + StdLibType::Result(inner) => { + inner.ok_ty.contains_owned_string_recursive() + || inner.err_ty.contains_owned_string_recursive() + } _ => false, }, BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(_shared_struct))) => { diff --git a/crates/swift-bridge-ir/src/bridged_type/boxed_fn.rs b/crates/swift-bridge-ir/src/bridged_type/boxed_fn.rs index 95672329..deeef229 100644 --- a/crates/swift-bridge-ir/src/bridged_type/boxed_fn.rs +++ b/crates/swift-bridge-ir/src/bridged_type/boxed_fn.rs @@ -1,8 +1,10 @@ use crate::bridged_type::{BridgedType, StdLibType, TypePosition}; use crate::parse::HostLang; +use crate::parsed_extern_fn::SwiftFuncGenerics; use crate::TypeDeclarations; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; +use std::collections::HashSet; use std::str::FromStr; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; @@ -116,13 +118,22 @@ impl BuiltInBoxedFnOnce { /// /// For example, `Box` would give us: /// arg0, unsafe { *Box::from_raw(arg1) } - pub fn to_rust_call_args(&self) -> Vec { + pub fn to_rust_call_args( + &self, + swift_bridge_path: &Path, + types: &TypeDeclarations, + ) -> Vec { self.params .iter() .enumerate() .map(|(idx, ty)| { let arg_name = Ident::new(&format!("arg{}", idx), Span::call_site()); - ty.convert_ffi_value_to_rust_value(&arg_name.to_token_stream(), arg_name.span()) + ty.convert_ffi_value_to_rust_value( + &arg_name.to_token_stream(), + arg_name.span(), + swift_bridge_path, + types, + ) }) .collect() } @@ -182,6 +193,37 @@ impl BuiltInBoxedFnOnce { _ => todo!("Not yet supported"), } } + + /// Generate the generate bounds for the Swift side. + /// For example: + /// "" + pub fn maybe_swift_generics(&self) -> String { + let mut maybe_generics = HashSet::new(); + + for bridged_arg in &self.params { + if bridged_arg.contains_owned_string_recursive() { + maybe_generics.insert(SwiftFuncGenerics::String); + } else if bridged_arg.contains_ref_string_recursive() { + maybe_generics.insert(SwiftFuncGenerics::Str); + } + } + + let maybe_generics = if maybe_generics.is_empty() { + "".to_string() + } else { + let mut m = vec![]; + + let generics: Vec = maybe_generics.into_iter().collect(); + + for generic in generics { + m.push(generic.as_bound()) + } + + format!("<{}>", m.join(", ")) + }; + + maybe_generics + } } impl BuiltInBoxedFnOnce { diff --git a/crates/swift-bridge-ir/src/bridged_type/bridged_option.rs b/crates/swift-bridge-ir/src/bridged_type/bridged_option.rs index acf0228c..e5e53f75 100644 --- a/crates/swift-bridge-ir/src/bridged_type/bridged_option.rs +++ b/crates/swift-bridge-ir/src/bridged_type/bridged_option.rs @@ -103,6 +103,9 @@ impl BridgedOption { StdLibType::Option(_) => { todo!("Support Option>") } + StdLibType::Result(_) => { + todo!("Support Option>") + } StdLibType::BoxedFnOnce(_) => { todo!("Option C>> is not yet supported") } @@ -197,6 +200,9 @@ impl BridgedOption { StdLibType::Option(_) => { todo!("Option> is not yet supported") } + StdLibType::Result(_) => { + todo!("Option> is not yet supported") + } StdLibType::BoxedFnOnce(_) => { todo!("Option C>> is not yet supported") } @@ -275,6 +281,9 @@ impl BridgedOption { StdLibType::Option(_) => { todo!("Support Option>") } + StdLibType::Result(_) => { + todo!("Option> is not yet supported") + } StdLibType::BoxedFnOnce(_) => { todo!("Option C>> is not yet supported") } @@ -369,6 +378,9 @@ impl BridgedOption { StdLibType::Option(_) => { todo!("Option is not yet supported") } + StdLibType::Result(_) => { + todo!("Option> is not yet supported") + } StdLibType::BoxedFnOnce(_) => { todo!("Option C>> is not yet supported") } @@ -440,6 +452,9 @@ impl BridgedOption { StdLibType::Option(_) => { todo!("Option> is not yet supported") } + StdLibType::Result(_) => { + todo!("Option> is not yet supported") + } StdLibType::BoxedFnOnce(_) => { todo!("Option C>> is not yet supported") } diff --git a/crates/swift-bridge-ir/src/bridged_type/bridged_result.rs b/crates/swift-bridge-ir/src/bridged_type/bridged_result.rs new file mode 100644 index 00000000..bca4d1a9 --- /dev/null +++ b/crates/swift-bridge-ir/src/bridged_type/bridged_result.rs @@ -0,0 +1,152 @@ +use crate::bridged_type::{BridgedType, TypePosition}; +use crate::TypeDeclarations; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::Path; + +/// Rust: Result +/// Swift: RustResult +/// +/// We don't use Swift's `Result` type since when we tried we saw a strange error +/// `'Sendable' class 'ResultTestOpaqueRustType' cannot inherit from another class other than 'NSObject'` +/// which meant that we could not use the `public class ResultTestOpaqueRustType: ResultTestOpaqueRustTypeRefMut {` +/// pattern that we use to prevent calling mutable methods on immutable references. +/// We only saw this error after `extension: ResultTestOpaqueRustType: Error {}` .. which was +/// necessary because Swift's Result type requires that the error implements the `Error` protocol. +#[derive(Debug, PartialEq, Clone)] +pub(crate) struct BuiltInResult { + pub ok_ty: Box, + pub err_ty: Box, +} + +impl BuiltInResult { + pub(super) fn to_ffi_compatible_rust_type(&self, swift_bridge_path: &Path) -> TokenStream { + // TODO: Choose the kind of Result representation based on whether or not the ok and error + // types are primitives. + // See `swift-bridge/src/std_bridge/result` + let result_kind = quote! { + ResultPtrAndPtr + }; + + quote! { + #swift_bridge_path::result::#result_kind + } + } + + pub(super) fn convert_ffi_value_to_rust_value( + &self, + expression: &TokenStream, + span: Span, + swift_bridge_path: &Path, + types: &TypeDeclarations, + ) -> TokenStream { + let ok_ffi_repr = self + .ok_ty + .to_ffi_compatible_rust_type(swift_bridge_path, types); + let err_ffi_repr = self + .err_ty + .to_ffi_compatible_rust_type(swift_bridge_path, types); + + let convert_ok = self.ok_ty.convert_ffi_value_to_rust_value( + "e! { #expression.ok_or_err as #ok_ffi_repr }, + span, + swift_bridge_path, + types, + ); + + let convert_err = self.err_ty.convert_ffi_value_to_rust_value( + "e! { #expression.ok_or_err as #err_ffi_repr }, + span, + swift_bridge_path, + types, + ); + + quote! { + if #expression.is_ok { + std::result::Result::Ok(#convert_ok) + } else { + std::result::Result::Err(#convert_err) + } + } + } + + pub fn to_rust_type_path(&self) -> TokenStream { + let ok = self.ok_ty.to_rust_type_path(); + let err = self.err_ty.to_rust_type_path(); + + quote! { Result<#ok, #err> } + } + + pub fn to_swift_type(&self, type_pos: TypePosition, types: &TypeDeclarations) -> String { + format!( + "RustResult<{}, {}>", + self.ok_ty.to_swift_type(type_pos, types), + self.err_ty.to_swift_type(type_pos, types), + ) + } + + pub fn convert_swift_expression_to_ffi_compatible( + &self, + expression: &str, + type_pos: TypePosition, + ) -> String { + let convert_ok = self + .ok_ty + .convert_swift_expression_to_ffi_compatible("ok", type_pos); + let convert_err = self + .err_ty + .convert_swift_expression_to_ffi_compatible("err", type_pos); + + format!( + "{{ switch {val} {{ case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: {convert_ok}) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: {convert_err}) }} }}()", + val = expression + ) + } + + pub fn to_c(&self) -> &'static str { + // TODO: Choose the kind of Result representation based on whether or not the ok and error + // types are primitives. + // See `swift-bridge/src/std_bridge/result` + "struct __private__ResultPtrAndPtr" + } +} + +impl BuiltInResult { + /// Go from `Result < A , B >` to a `BuiltInResult`. + pub fn from_str_tokens(string: &str, types: &TypeDeclarations) -> Option { + // A , B > + let trimmed = string.trim_start_matches("Result < "); + // A , B + let trimmed = trimmed.trim_end_matches(" >"); + + // [A, B] + let mut ok_and_err = trimmed.split(","); + let ok = ok_and_err.next()?.trim(); + let err = ok_and_err.next()?.trim(); + + let ok = BridgedType::new_with_str(ok, types)?; + let err = BridgedType::new_with_str(err, types)?; + + Some(BuiltInResult { + ok_ty: Box::new(ok), + err_ty: Box::new(err), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quote::ToTokens; + + /// Verify that we can parse a `Result<(), ()>` + #[test] + fn result_from_null_type() { + let tokens = quote! { Result<(), ()> }.to_token_stream().to_string(); + + let result = BuiltInResult::from_str_tokens(&tokens, &TypeDeclarations::default()).unwrap(); + + assert!(result.ok_ty.is_null()); + assert!(result.err_ty.is_null()); + } +} diff --git a/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs b/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs index d49af2ae..f046be02 100644 --- a/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs +++ b/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs @@ -70,6 +70,7 @@ impl SharedStruct { pub(crate) fn convert_ffi_repr_to_rust( &self, rust_val: &TokenStream, + swift_bridge_path: &Path, types: &TypeDeclarations, ) -> TokenStream { let struct_name = &self.name; @@ -83,8 +84,12 @@ impl SharedStruct { let access_field = norm_field.append_field_accessor("e! {val}); let ty = BridgedType::new_with_type(&norm_field.ty, types).unwrap(); - let converted_field = - ty.convert_ffi_value_to_rust_value(&access_field, norm_field.ty.span()); + let converted_field = ty.convert_ffi_value_to_rust_value( + &access_field, + norm_field.ty.span(), + swift_bridge_path, + types, + ); quote! { #maybe_name_and_colon #converted_field diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests.rs index 49c66146..cb9fa183 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests.rs @@ -29,7 +29,7 @@ use crate::test_utils::{ mod already_declared_attribute_codegen_tests; mod async_function_codegen_tests; -mod boxed_fnonce_tests; +mod boxed_fnonce_codegen_tests; mod conditional_compilation_codegen_tests; mod extern_rust_function_opaque_rust_type_argument_codegen_tests; mod extern_rust_function_opaque_rust_type_return_codegen_tests; @@ -40,6 +40,7 @@ mod generic_opaque_rust_type_codegen_tests; mod opaque_rust_type_codegen_tests; mod opaque_swift_type_codegen_tests; mod option_codegen_tests; +mod result_codegen_tests; mod shared_enum_codegen_tests; mod shared_struct_codegen_tests; mod string_codegen_tests; diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/boxed_fnonce_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/boxed_fnonce_codegen_tests.rs similarity index 87% rename from crates/swift-bridge-ir/src/codegen/codegen_tests/boxed_fnonce_tests.rs rename to crates/swift-bridge-ir/src/codegen/codegen_tests/boxed_fnonce_codegen_tests.rs index 03f5e858..e3a299ba 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/boxed_fnonce_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/boxed_fnonce_codegen_tests.rs @@ -482,6 +482,115 @@ void __swift_bridge__$some_function$_free$param0(void* some_function_callback); } } +/// Verify that we can pass a callback with an opaque Rust arg from Rust to Swift. +mod test_swift_takes_callback_with_result_arg { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + extern "Swift" { + fn some_function(callback: Box) -> ()>); + } + + extern "Rust" { + type ARustType; + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::ContainsMany(vec![ + quote! { + pub fn some_function (callback: Box) -> ()>) { + unsafe { + __swift_bridge__some_function( + Box::into_raw(Box::new(callback)) as *mut Box) -> ()> + ) + } + } + }, + quote! { + #[export_name = "__swift_bridge__$some_function$param0"] + pub extern "C" fn some_function_param0(some_function_callback: *mut Box) -> ()>, arg0: swift_bridge::result::ResultPtrAndPtr) { + unsafe { Box::from_raw(some_function_callback)( + if arg0.is_ok { + std::result::Result::Ok(unsafe { *Box::from_raw(arg0.ok_or_err as *mut super::ARustType) }) + } else { + std::result::Result::Err(unsafe { *Box::from_raw(arg0.ok_or_err as *mut super::ARustType) }) + } + )} + } + + #[export_name = "__swift_bridge__$some_function$_free$param0"] + pub extern "C" fn free_some_function_param0(some_function_callback: *mut Box) -> ()>) { + let _ = unsafe { Box::from_raw(some_function_callback) }; + } + }, + quote! { + #[link_name = "__swift_bridge__$some_function"] + fn __swift_bridge__some_function(callback: *mut Box) -> ()>); + }, + ]) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsManyAfterTrim(vec![ + r#" +class __private__RustFnOnceCallback$some_function$param0 { + var ptr: UnsafeMutableRawPointer + var called = false + + init(ptr: UnsafeMutableRawPointer) { + self.ptr = ptr + } + + deinit { + if !called { + __swift_bridge__$some_function$_free$param0(ptr) + } + } + + func call(_ arg0: RustResult) { + if called { + fatalError("Cannot call a Rust FnOnce function twice") + } + called = true + return __swift_bridge__$some_function$param0(ptr, { switch arg0 { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: {ok.isOwned = false; return ok.ptr;}()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: {err.isOwned = false; return err.ptr;}()) } }()) + } +} + "#, + r#" +@_cdecl("__swift_bridge__$some_function") +func __swift_bridge__some_function (_ callback: UnsafeMutableRawPointer) { + { let cb0 = __private__RustFnOnceCallback$some_function$param0(ptr: callback); let _ = some_function(callback: { arg0 in cb0.call(arg0) }) }() +} +"#, + ]) + } + + fn expected_c_header() -> ExpectedCHeader { + ExpectedCHeader::ContainsAfterTrim( + r#" +void __swift_bridge__$some_function$param0(void* some_function_callback, struct __private__ResultPtrAndPtr arg0); +void __swift_bridge__$some_function$_free$param0(void* some_function_callback); +"#, + ) + } + + #[test] + fn test_swift_takes_callback_with_result_arg() { + 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(); + } +} + /// Verify that we can pass two callbacks from Rust to Swift. /// /// We put a callback that takes arguments in the second position to ensure that our codegen 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 new file mode 100644 index 00000000..6e2fc22f --- /dev/null +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs @@ -0,0 +1,127 @@ +//! See also: crates/swift-integration-tests/src/result.rs + +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 +/// where T and E are Strings. +mod extern_rust_fn_result_string { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + extern "Rust" { + fn some_function (arg: Result); + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + #[export_name = "__swift_bridge__$some_function"] + pub extern "C" fn __swift_bridge__some_function( + arg: swift_bridge::result::ResultPtrAndPtr + ) { + super::some_function( + if arg.is_ok { + std::result::Result::Ok(unsafe { Box::from_raw(arg.ok_or_err as *mut swift_bridge::string::RustString).0 }) + } else { + std::result::Result::Err(unsafe { Box::from_raw(arg.ok_or_err as *mut swift_bridge::string::RustString).0 }) + } + ) + } + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +func some_function(_ arg: RustResult) { + __swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: { let rustString = ok.intoRustString(); rustString.isOwned = false; return rustString.ptr }()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: { let rustString = err.intoRustString(); rustString.isOwned = false; return rustString.ptr }()) } }()) +} +"#, + ) + } + + const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim( + r#" +void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg); + "#, + ); + + #[test] + fn extern_rust_fn_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 and returns a Result +/// where T and E are opaque Rust types. +mod extern_rust_fn_result_opaque_rust { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + extern "Rust" { + type SomeType; + + fn some_function (arg: Result); + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + #[export_name = "__swift_bridge__$some_function"] + pub extern "C" fn __swift_bridge__some_function( + arg: swift_bridge::result::ResultPtrAndPtr + ) { + super::some_function( + if arg.is_ok { + std::result::Result::Ok(unsafe { *Box::from_raw(arg.ok_or_err as *mut super::SomeType) }) + } else { + std::result::Result::Err(unsafe { *Box::from_raw(arg.ok_or_err as *mut super::SomeType) }) + } + ) + } + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +func some_function(_ arg: RustResult) { + __swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: {ok.isOwned = false; return ok.ptr;}()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: {err.isOwned = false; return err.ptr;}()) } }()) +} +"#, + ) + } + + const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim( + r#" +void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg); + "#, + ); + + #[test] + fn extern_rust_fn_result_opaque_rust() { + 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(); + } +} diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs index c8ae45b9..c58d13e6 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs @@ -65,8 +65,11 @@ impl SwiftBridgeModule { &self.types, &self.swift_bridge_path, ); - let convert_ffi_to_rust = - shared_struct.convert_ffi_repr_to_rust("e! { self }, &self.types); + let convert_ffi_to_rust = shared_struct.convert_ffi_repr_to_rust( + "e! { self }, + swift_bridge_path, + &self.types, + ); let struct_ffi_repr = if shared_struct.fields.is_empty() { // Using a u8 is arbitrary... We just need a field since empty structs aren't FFI safe. diff --git a/crates/swift-bridge-ir/src/codegen/generate_swift.rs b/crates/swift-bridge-ir/src/codegen/generate_swift.rs index eb6ae2ea..484be7dd 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_swift.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_swift.rs @@ -187,21 +187,6 @@ func {fn_name} (ptr: UnsafeMutableRawPointer) {{ ) } -#[derive(Hash, Eq, PartialEq, Ord, PartialOrd)] -enum SwiftFuncGenerics { - String, - Str, -} - -impl SwiftFuncGenerics { - fn as_bound(&self) -> &'static str { - match self { - SwiftFuncGenerics::String => "GenericIntoRustString: IntoRustString", - SwiftFuncGenerics::Str => "GenericToRustStr: ToRustStr", - } - } -} - fn gen_function_exposes_swift_to_rust( func: &ParsedExternFn, types: &TypeDeclarations, @@ -293,6 +278,8 @@ fn gen_function_exposes_swift_to_rust( TypePosition::FnReturn(HostLang::Swift), ); + let maybe_generics = boxed_fn.maybe_swift_generics(); + rust_fn_once_callback_classes += &format!( r#" class __private__RustFnOnceCallback{maybe_associated_ty}${fn_name}$param{idx} {{ @@ -309,7 +296,7 @@ class __private__RustFnOnceCallback{maybe_associated_ty}${fn_name}$param{idx} {{ }} }} - func call({params_as_swift}){maybe_ret} {{ + func call{maybe_generics}({params_as_swift}){maybe_ret} {{ if called {{ fatalError("Cannot call a Rust FnOnce function twice") }} diff --git a/crates/swift-bridge-ir/src/codegen/generate_swift/generate_function_swift_calls_rust.rs b/crates/swift-bridge-ir/src/codegen/generate_swift/generate_function_swift_calls_rust.rs index 587d8c66..2a928199 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_swift/generate_function_swift_calls_rust.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_swift/generate_function_swift_calls_rust.rs @@ -1,9 +1,7 @@ use crate::bridged_type::{fn_arg_name, BridgedType, StdLibType, TypePosition}; -use crate::codegen::generate_swift::SwiftFuncGenerics; use crate::parse::{HostLang, TypeDeclaration}; use crate::{ParsedExternFn, TypeDeclarations, SWIFT_BRIDGE_PREFIX}; use quote::ToTokens; -use std::collections::HashSet; use std::ops::Deref; use syn::{Path, ReturnType, Type}; @@ -136,8 +134,6 @@ pub(super) fn gen_func_swift_calls_rust( "return " }; - let mut maybe_generics = HashSet::new(); - for arg in function.func.sig.inputs.iter() { let bridged_arg = BridgedType::new_with_fn_arg(arg, types); if bridged_arg.is_none() { @@ -147,12 +143,6 @@ pub(super) fn gen_func_swift_calls_rust( let arg_name = fn_arg_name(arg).unwrap().to_string(); - if bridged_arg.contains_owned_string_recursive() { - maybe_generics.insert(SwiftFuncGenerics::String); - } else if bridged_arg.contains_ref_string_recursive() { - maybe_generics.insert(SwiftFuncGenerics::Str); - } - // TODO: Refactor to make less duplicative match bridged_arg { BridgedType::StdLib(StdLibType::Str) => { @@ -197,19 +187,7 @@ pub(super) fn gen_func_swift_calls_rust( function.to_swift_return_type(types) }; - let maybe_generics = if maybe_generics.is_empty() { - "".to_string() - } else { - let mut m = vec![]; - - let generics: Vec = maybe_generics.into_iter().collect(); - - for generic in generics { - m.push(generic.as_bound()) - } - - format!("<{}>", m.join(", ")) - }; + let maybe_generics = function.maybe_swift_generics(types); let func_definition = if function.sig.asyncness.is_some() { let func_ret_ty = function.return_ty_built_in(types).unwrap(); diff --git a/crates/swift-bridge-ir/src/parsed_extern_fn.rs b/crates/swift-bridge-ir/src/parsed_extern_fn.rs index 582e8666..3bec7741 100644 --- a/crates/swift-bridge-ir/src/parsed_extern_fn.rs +++ b/crates/swift-bridge-ir/src/parsed_extern_fn.rs @@ -4,6 +4,7 @@ use crate::parse::{HostLang, SharedTypeDeclaration, TypeDeclaration, TypeDeclara use crate::SWIFT_BRIDGE_PREFIX; use proc_macro2::{Ident, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; +use std::collections::HashSet; use std::ops::Deref; use syn::spanned::Spanned; use syn::{FnArg, ForeignItemFn, Lifetime, Path, ReturnType, Token, Type}; @@ -13,6 +14,21 @@ mod to_extern_c_param_names_and_types; mod to_rust_impl_call_swift; mod to_swift_func; +#[derive(Hash, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) enum SwiftFuncGenerics { + String, + Str, +} + +impl SwiftFuncGenerics { + pub fn as_bound(&self) -> &'static str { + match self { + SwiftFuncGenerics::String => "GenericIntoRustString: IntoRustString", + SwiftFuncGenerics::Str => "GenericToRustStr: ToRustStr", + } + } +} + /// A method or associated function associated with a type. /// /// fn bar (&self); @@ -256,7 +272,12 @@ impl ParsedExternFn { if let Some(built_in) = BridgedType::new_with_type(&pat_ty.ty, types) { if self.host_lang.is_rust() { - arg = built_in.convert_ffi_value_to_rust_value(&arg, pat_ty.ty.span()); + arg = built_in.convert_ffi_value_to_rust_value( + &arg, + pat_ty.ty.span(), + swift_bridge_path, + types, + ); if self.args_into_contains_arg(fn_arg) { arg = quote_spanned! {pat_ty.span()=> @@ -562,6 +583,43 @@ void {free_boxed_fn_link_name}(void* {boxed_fn_arg_name});"# pub fn arg_name_at_idx(&self, idx: usize) -> Option { self.arg_name_tokens_at_idx(idx).map(|a| a.to_string()) } + + /// Generate the generate bounds for a Swift function. + /// For example: + /// "" + pub fn maybe_swift_generics(&self, types: &TypeDeclarations) -> String { + let mut maybe_generics = HashSet::new(); + + for arg in self.sig.inputs.iter() { + let bridged_arg = BridgedType::new_with_fn_arg(arg, types); + if bridged_arg.is_none() { + continue; + } + + let bridged_arg = bridged_arg.unwrap(); + if bridged_arg.contains_owned_string_recursive() { + maybe_generics.insert(SwiftFuncGenerics::String); + } else if bridged_arg.contains_ref_string_recursive() { + maybe_generics.insert(SwiftFuncGenerics::Str); + } + } + + let maybe_generics = if maybe_generics.is_empty() { + "".to_string() + } else { + let mut m = vec![]; + + let generics: Vec = maybe_generics.into_iter().collect(); + + for generic in generics { + m.push(generic.as_bound()) + } + + format!("<{}>", m.join(", ")) + }; + + maybe_generics + } } impl Deref for ParsedExternFn { diff --git a/crates/swift-bridge-ir/src/parsed_extern_fn/to_rust_impl_call_swift.rs b/crates/swift-bridge-ir/src/parsed_extern_fn/to_rust_impl_call_swift.rs index 96d5a6e9..951f7060 100644 --- a/crates/swift-bridge-ir/src/parsed_extern_fn/to_rust_impl_call_swift.rs +++ b/crates/swift-bridge-ir/src/parsed_extern_fn/to_rust_impl_call_swift.rs @@ -64,7 +64,12 @@ impl ParsedExternFn { }; if let Some(built_in) = BridgedType::new_with_return_type(&sig.output, types) { - inner = built_in.convert_ffi_value_to_rust_value(&inner, sig.output.span()); + inner = built_in.convert_ffi_value_to_rust_value( + &inner, + sig.output.span(), + swift_bridge_path, + types, + ); } else { todo!("Push to ParsedErrors") } @@ -114,7 +119,7 @@ impl ParsedExternFn { let free_boxed_fn_name = Ident::new(&free_boxed_fn_name, fn_name.span()); let params = boxed_fn.params_to_ffi_compatible_rust_types(swift_bridge_path, types); - let call_args = boxed_fn.to_rust_call_args(); + let call_args = boxed_fn.to_rust_call_args(swift_bridge_path, types); let call_boxed_fn_link_name = self.call_boxed_fn_link_name(idx); let free_boxed_fn_link_name = self.free_boxed_fn_link_name(idx); diff --git a/crates/swift-integration-tests/src/boxed_functions.rs b/crates/swift-integration-tests/src/boxed_functions.rs index 9db4f3f1..e3024d88 100644 --- a/crates/swift-integration-tests/src/boxed_functions.rs +++ b/crates/swift-integration-tests/src/boxed_functions.rs @@ -15,6 +15,10 @@ mod ffi { -> u16; fn swift_calls_rust_fnonce_callback_twice(arg: Box ()>); + + fn swift_func_takes_callback_with_result_arg( + arg: Box)>, + ); } extern "Swift" { @@ -128,4 +132,8 @@ fn test_callbacks_rust_calls_swift() { let five_times_two = swift_callback_tester.method_with_fnonce_callback_primitive(Box::new(|num| num * 2)); assert_eq!(five_times_two, 10); + + ffi::swift_func_takes_callback_with_result_arg(Box::new(|result| { + assert_eq!(result.unwrap().val(), 555) + })); } diff --git a/crates/swift-integration-tests/src/lib.rs b/crates/swift-integration-tests/src/lib.rs index 387fe163..3eccc0f8 100644 --- a/crates/swift-integration-tests/src/lib.rs +++ b/crates/swift-integration-tests/src/lib.rs @@ -8,6 +8,7 @@ mod conditional_compilation; mod generics; mod option; mod pointer; +mod result; mod rust_function_uses_opaque_swift_type; mod shared_types; mod slice; diff --git a/crates/swift-integration-tests/src/result.rs b/crates/swift-integration-tests/src/result.rs new file mode 100644 index 00000000..0a1b106e --- /dev/null +++ b/crates/swift-integration-tests/src/result.rs @@ -0,0 +1,51 @@ +//! See also: crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs + +#[swift_bridge::bridge] +mod ffi { + extern "Rust" { + fn rust_func_takes_result_string(arg: Result); + fn rust_func_takes_result_opaque_rust( + arg: Result, + ); + } + + extern "Rust" { + type ResultTestOpaqueRustType; + + #[swift_bridge(init)] + fn new(val: u32) -> ResultTestOpaqueRustType; + } +} + +fn rust_func_takes_result_string(arg: Result) { + match arg { + Ok(ok) => { + assert_eq!(ok, "Success Message") + } + Err(err) => { + assert_eq!(err, "Error Message") + } + } +} + +fn rust_func_takes_result_opaque_rust( + arg: Result, +) { + match arg { + Ok(ok) => { + assert_eq!(ok.val, 111) + } + Err(err) => { + assert_eq!(err.val, 222) + } + } +} + +pub struct ResultTestOpaqueRustType { + val: u32, +} +impl ResultTestOpaqueRustType { + fn new(val: u32) -> Self { + Self { val } + } +} diff --git a/src/lib.rs b/src/lib.rs index 9a42cb10..85788076 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ pub use swift_bridge_macro::bridge; mod std_bridge; -pub use self::std_bridge::{option, string}; +pub use self::std_bridge::{option, result, string}; #[doc(hidden)] #[cfg(feature = "async")] diff --git a/src/std_bridge.rs b/src/std_bridge.rs index 272e962d..4f31318a 100644 --- a/src/std_bridge.rs +++ b/src/std_bridge.rs @@ -1,5 +1,6 @@ #![allow(missing_docs)] pub mod option; +pub mod result; mod rust_vec; pub mod string; diff --git a/src/std_bridge/result.rs b/src/std_bridge/result.rs new file mode 100644 index 00000000..44e84edf --- /dev/null +++ b/src/std_bridge/result.rs @@ -0,0 +1,34 @@ +#[repr(C)] +#[doc(hidden)] +// Bridges `Result` where `T` and `E` are non primitive types. +pub struct ResultPtrAndPtr { + pub is_ok: bool, + pub ok_or_err: *mut std::ffi::c_void, +} + +// TODO: We need to define every combination of primitive and pointer. +// Probably low priority since most users are probably using non-primitive types for +// `Result`. +// +// #[repr(C)] +// #[doc(hidden)] +// pub struct ResultU8AndU8 { +// pub is_ok: bool, +// pub ok_or_err: u8, +// } +// +// #[repr(C)] +// #[doc(hidden)] +// pub struct ResultU8AndU16 { +// pub is_ok: bool, +// pub ok: u8, +// pub err: u16 +// } +// +// #[repr(C)] +// #[doc(hidden)] +// pub struct ResultU8AndPtr { +// pub is_ok: bool, +// pub ok: u8, +// pub err: *mut std::ffi::c_void, +// }