From bcde7b26449bd8664a647c0ae5b014ecda6714c3 Mon Sep 17 00:00:00 2001 From: NiwakaDev Date: Sat, 10 Aug 2024 16:30:52 +0900 Subject: [PATCH 1/6] Support throwing initializers --- .../ResultTests.swift | 23 ++++++ crates/swift-bridge-ir/src/bridged_type.rs | 9 +++ .../src/bridged_type/bridgeable_result.rs | 14 ++++ .../src/bridged_type/bridgeable_string.rs | 4 + .../src/bridged_type/bridged_opaque_type.rs | 4 + .../src/bridged_type/bridged_option.rs | 2 + .../src/bridged_type/built_in_tuple.rs | 2 + .../opaque_rust_type_codegen_tests.rs | 79 +++++++++++++++++++ .../generate_function_swift_calls_rust.rs | 34 ++++++-- .../src/parse/parse_extern_mod.rs | 52 ++++++++---- .../parse_extern_mod/function_attributes.rs | 6 +- .../swift-bridge-ir/src/parsed_extern_fn.rs | 10 ++- crates/swift-integration-tests/src/result.rs | 24 ++++++ 13 files changed, 240 insertions(+), 23 deletions(-) diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift index 17728902..058ec2e1 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift @@ -249,4 +249,27 @@ 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") { + _ in + do { + let throwingInitializer = try ThrowingInitializer(false) + } catch let error as ResultTransparentEnum { + if case .NamedField(data: -123) = error { + // This case should pass. + } else { + XCTFail() + } + } catch { + XCTFail() + } + } + XCTContext.runActivity(named: "Should succeed") { + _ in + let throwingInitializer = try! ThrowingInitializer(true) + XCTAssertEqual(throwingInitializer.val(), 123) + } + } } diff --git a/crates/swift-bridge-ir/src/bridged_type.rs b/crates/swift-bridge-ir/src/bridged_type.rs index 6b423f85..39f23e3c 100644 --- a/crates/swift-bridge-ir/src/bridged_type.rs +++ b/crates/swift-bridge-ir/src/bridged_type.rs @@ -411,6 +411,7 @@ pub(crate) enum TypePosition { FnReturn(HostLang), SharedStructField, SwiftCallsRustAsyncOnCompleteReturnTy, + ThrowingInit(HostLang), } /// &[T] @@ -1177,6 +1178,7 @@ impl BridgedType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } StdLibType::Null => "()".to_string(), @@ -1193,6 +1195,7 @@ impl BridgedType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), }, StdLibType::Vec(ty) => match type_pos { TypePosition::FnArg(func_host_lang, _) => { @@ -1215,6 +1218,7 @@ impl BridgedType { "UnsafeMutableRawPointer".to_string() } } + TypePosition::ThrowingInit(_) => unimplemented!(), _ => { format!( "RustVec<{}>", @@ -1243,6 +1247,7 @@ impl BridgedType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { shared_struct.ffi_name_string() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Enum(shared_enum))) => { @@ -1259,6 +1264,7 @@ impl BridgedType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } } @@ -1567,6 +1573,7 @@ impl BridgedType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), }, PointerKind::Mut => expression.to_string(), }, @@ -1660,6 +1667,7 @@ impl BridgedType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), }, }, StdLibType::Str => match type_pos { @@ -1677,6 +1685,7 @@ impl BridgedType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), }, StdLibType::Vec(_) => { format!( diff --git a/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs b/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs index 8caa85e2..55d794de 100644 --- a/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs +++ b/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs @@ -1,4 +1,5 @@ use crate::bridged_type::{BridgeableType, BridgedType, CFfiStruct, TypePosition}; +use crate::parse::HostLang; use crate::{TypeDeclarations, SWIFT_BRIDGE_PREFIX}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; @@ -207,6 +208,7 @@ impl BuiltInResult { } "__private__ResultPtrAndPtr".to_string() } + TypePosition::ThrowingInit(_) => todo!(), } } @@ -253,6 +255,18 @@ impl BuiltInResult { ), TypePosition::SharedStructField => todo!(), TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => todo!(), + TypePosition::ThrowingInit(lang) => { + match lang { + HostLang::Rust => format!( + "let val = {expression}; switch val.tag {{ case {c_ok_name}: self.init(ptr: val.payload.ok) case {c_err_name}: throw {err_swift_type} default: fatalError() }}", + expression = expression, + c_ok_name = c_ok_name, + c_err_name = c_err_name, + err_swift_type = err_swift_type + ), + HostLang::Swift => todo!(), + } + } }; } diff --git a/crates/swift-bridge-ir/src/bridged_type/bridgeable_string.rs b/crates/swift-bridge-ir/src/bridged_type/bridgeable_string.rs index b5bca782..29e9f59a 100644 --- a/crates/swift-bridge-ir/src/bridged_type/bridgeable_string.rs +++ b/crates/swift-bridge-ir/src/bridged_type/bridgeable_string.rs @@ -76,6 +76,7 @@ impl BridgeableType for BridgedString { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { "UnsafeMutableRawPointer?".to_string() } + TypePosition::ThrowingInit(_) => todo!(), } } @@ -130,6 +131,7 @@ impl BridgeableType for BridgedString { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { todo!() } + TypePosition::ThrowingInit(_) => todo!(), } } @@ -205,6 +207,7 @@ impl BridgeableType for BridgedString { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => todo!(), } } @@ -250,6 +253,7 @@ impl BridgeableType for BridgedString { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { format!("RustString(ptr: {}!)", expression) } + TypePosition::ThrowingInit(_) => todo!(), } } diff --git a/crates/swift-bridge-ir/src/bridged_type/bridged_opaque_type.rs b/crates/swift-bridge-ir/src/bridged_type/bridged_opaque_type.rs index 053fe6fe..15049587 100644 --- a/crates/swift-bridge-ir/src/bridged_type/bridged_opaque_type.rs +++ b/crates/swift-bridge-ir/src/bridged_type/bridged_opaque_type.rs @@ -129,6 +129,7 @@ impl BridgeableType for OpaqueForeignType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } else { match type_pos { @@ -146,6 +147,7 @@ impl BridgeableType for OpaqueForeignType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } } @@ -396,6 +398,7 @@ impl BridgeableType for OpaqueForeignType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } } else { @@ -420,6 +423,7 @@ impl BridgeableType for OpaqueForeignType { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } } 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 016b2c0d..8bfa38cb 100644 --- a/crates/swift-bridge-ir/src/bridged_type/bridged_option.rs +++ b/crates/swift-bridge-ir/src/bridged_type/bridged_option.rs @@ -324,6 +324,7 @@ impl BridgedOption { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { todo!() } + TypePosition::ThrowingInit(_) => todo!(), }, StdLibType::Vec(_) => { format!( @@ -397,6 +398,7 @@ impl BridgedOption { TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => { unimplemented!() } + TypePosition::ThrowingInit(_) => unimplemented!(), } } diff --git a/crates/swift-bridge-ir/src/bridged_type/built_in_tuple.rs b/crates/swift-bridge-ir/src/bridged_type/built_in_tuple.rs index bc28dbff..6911d8a3 100644 --- a/crates/swift-bridge-ir/src/bridged_type/built_in_tuple.rs +++ b/crates/swift-bridge-ir/src/bridged_type/built_in_tuple.rs @@ -135,6 +135,7 @@ impl BridgeableType for BuiltInTuple { } TypePosition::SharedStructField => todo!(), TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => todo!(), + TypePosition::ThrowingInit(_) => todo!(), } } @@ -227,6 +228,7 @@ impl BridgeableType for BuiltInTuple { } TypePosition::SharedStructField => todo!(), TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => todo!(), + TypePosition::ThrowingInit(_) => todo!(), } } diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs index 2d2a6f03..40074f04 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs @@ -527,3 +527,82 @@ void* __swift_bridge__$Foo$new(void); .test(); } } + +/// Verify that we can use a Swift class with a throwing init. +mod extern_rust_class_with_throwing_init { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod foo { + enum SomeErrEnum { + Variant1, + Variant2, + } + extern "Rust" { + type Foo; + + #[swift_bridge(init)] + fn new() -> Result; + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + # [export_name = "__swift_bridge__$Foo$new"] + pub extern "C" fn __swift_bridge__Foo_new() -> ResultFooAndSomeErrEnum{ + match super :: Foo :: new() { + Ok(ok) => ResultFooAndSomeErrEnum::Ok(Box::into_raw(Box::new({ + let val: super::Foo = ok; + val + })) as *mut super::Foo), + Err(err) => ResultFooAndSomeErrEnum::Err(err.into_ffi_repr()), + } + } + }) + } + + const EXPECTED_SWIFT: ExpectedSwiftCode = ExpectedSwiftCode::ContainsAfterTrim( + r#" +public class Foo: FooRefMut { + var isOwned: Bool = true + + public override init(ptr: UnsafeMutableRawPointer) { + super.init(ptr: ptr) + } + + deinit { + if isOwned { + __swift_bridge__$Foo$_free(ptr) + } + } +} +extension Foo { + public convenience init() throws { + let val = __swift_bridge__$Foo$new(); switch val.tag { case __swift_bridge__$ResultFooAndSomeErrEnum$ResultOk: self.init(ptr: val.payload.ok) case __swift_bridge__$ResultFooAndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } + } +} +"#, + ); + + const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim( + r#" +typedef enum __swift_bridge__$ResultFooAndSomeErrEnum$Tag {__swift_bridge__$ResultFooAndSomeErrEnum$ResultOk, __swift_bridge__$ResultFooAndSomeErrEnum$ResultErr} __swift_bridge__$ResultFooAndSomeErrEnum$Tag; +union __swift_bridge__$ResultFooAndSomeErrEnum$Fields {void* ok; struct __swift_bridge__$SomeErrEnum err;}; +typedef struct __swift_bridge__$ResultFooAndSomeErrEnum{__swift_bridge__$ResultFooAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultFooAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultFooAndSomeErrEnum; +"#, + ); + + #[test] + fn extern_rust_class_with_throwing_init() { + CodegenTest { + bridge_module: bridge_module_tokens().into(), + expected_rust_tokens: expected_rust_tokens(), + expected_swift_code: EXPECTED_SWIFT, + expected_c_header: EXPECTED_C_HEADER, + } + .test(); + } +} 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 a52a9933..07bcad4f 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,5 +1,6 @@ use crate::bridged_type::{fn_arg_name, BridgeableType, BridgedType, StdLibType, TypePosition}; use crate::parse::{HostLang, TypeDeclaration}; +use crate::parsed_extern_fn::FailableInitializerType; use crate::{ParsedExternFn, TypeDeclarations, SWIFT_BRIDGE_PREFIX}; use quote::ToTokens; use std::ops::Deref; @@ -55,7 +56,13 @@ pub(super) fn gen_func_swift_calls_rust( if function.is_copy_method_on_opaque_type() { "public init".to_string() } else { - if function.is_swift_failable_initializer { + if let Some(crate::parsed_extern_fn::FailableInitializerType::Throwing) = + function.swift_failable_initializer + { + "public convenience init".to_string() + } else if let Some(crate::parsed_extern_fn::FailableInitializerType::Option) = + function.swift_failable_initializer + { "public convenience init?".to_string() } else { "public convenience init".to_string() @@ -69,6 +76,12 @@ pub(super) fn gen_func_swift_calls_rust( } }; + let maybe_throws = + if let Some(FailableInitializerType::Throwing) = function.swift_failable_initializer { + " throws" + } else { + "" + }; let indentation = if function.associated_type.is_some() { " " } else { @@ -84,7 +97,17 @@ pub(super) fn gen_func_swift_calls_rust( let mut call_rust = if function.sig.asyncness.is_some() { call_rust } else if function.is_swift_initializer { - call_rust + if let Some(FailableInitializerType::Throwing) = function.swift_failable_initializer { + let built_in = function.return_ty_built_in(types).unwrap(); + built_in.convert_ffi_value_to_swift_value( + &call_rust, + TypePosition::ThrowingInit(function.host_lang), + types, + swift_bridge_path, + ) + } else { + call_rust + } } else if let Some(built_in) = function.return_ty_built_in(types) { built_in.convert_ffi_value_to_swift_value( &call_rust, @@ -183,12 +206,12 @@ pub(super) fn gen_func_swift_calls_rust( if function.is_copy_method_on_opaque_type() { call_rust = format!("self.bytes = {}", call_rust) } else { - if function.is_swift_failable_initializer { + if let Some(FailableInitializerType::Option) = function.swift_failable_initializer { call_rust = format!( "guard let val = {} else {{ return nil }}; self.init(ptr: val)", call_rust ) - } else { + } else if function.swift_failable_initializer.is_none() { call_rust = format!("self.init(ptr: {})", call_rust) } } @@ -315,7 +338,7 @@ return{maybe_try}await {with_checked_continuation_function_name}({{ (continuatio ) } else { format!( - r#"{indentation}{maybe_static_class_func}{swift_class_func_name}{maybe_generics}({params}){maybe_ret} {{ + r#"{indentation}{maybe_static_class_func}{swift_class_func_name}{maybe_generics}({params}){maybe_throws}{maybe_ret} {{ {indentation} {call_rust} {indentation}}}"#, indentation = indentation, @@ -325,6 +348,7 @@ return{maybe_try}await {with_checked_continuation_function_name}({{ (continuatio params = params, maybe_ret = maybe_return, call_rust = call_rust, + maybe_throws = maybe_throws, ) }; diff --git a/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs b/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs index b15c1cfd..37a1a84e 100644 --- a/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs +++ b/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs @@ -10,7 +10,7 @@ use crate::parse::type_declarations::{ OpaqueForeignTypeDeclaration, TypeDeclaration, TypeDeclarations, }; use crate::parse::{HostLang, OpaqueRustTypeGenerics}; -use crate::parsed_extern_fn::fn_arg_is_mutable_reference; +use crate::parsed_extern_fn::{fn_arg_is_mutable_reference, FailableInitializerType}; use crate::ParsedExternFn; use proc_macro2::Ident; use quote::{format_ident, ToTokens}; @@ -111,13 +111,17 @@ impl<'a> ForeignModParser<'a> { } let return_type = &func.sig.output; - let mut is_swift_failable_initializer = false; + let mut swift_failable_initializer: Option = None; if let ReturnType::Type(_, return_ty) = return_type { let bridged_return_type = BridgedType::new_with_type(return_ty.deref(), &self.type_declarations); + if let Some(ty) = &bridged_return_type { if ty.as_option().is_some() && attributes.is_swift_initializer { - is_swift_failable_initializer = true; + swift_failable_initializer = Some(FailableInitializerType::Option); + } else if ty.as_result().is_some() && attributes.is_swift_initializer { + swift_failable_initializer = + Some(FailableInitializerType::Throwing); } } if bridged_return_type.is_none() { @@ -131,7 +135,7 @@ impl<'a> ForeignModParser<'a> { func.clone(), &attributes, &mut local_type_declarations, - is_swift_failable_initializer, + swift_failable_initializer.clone(), )?; if attributes.is_swift_identifiable { @@ -236,7 +240,7 @@ impl<'a> ForeignModParser<'a> { func, associated_type, is_swift_initializer: attributes.is_swift_initializer, - is_swift_failable_initializer: is_swift_failable_initializer, + swift_failable_initializer, is_swift_identifiable: attributes.is_swift_identifiable, host_lang, rust_name_override: attributes.rust_name, @@ -302,7 +306,7 @@ impl<'a> ForeignModParser<'a> { func: ForeignItemFn, attributes: &FunctionAttributes, local_type_declarations: &mut HashMap, - is_swift_failable_initializer: bool, + swift_failable_initializer: Option, ) -> syn::Result> { let associated_type = match first { Some(FnArg::Receiver(recv)) => { @@ -346,7 +350,7 @@ impl<'a> ForeignModParser<'a> { func.clone(), attributes, local_type_declarations, - is_swift_failable_initializer, + swift_failable_initializer, )?; associated_type } @@ -383,14 +387,32 @@ Otherwise we use a more general error that says that your argument is invalid. ty_string } }; - if is_swift_failable_initializer { - // Safety: since we've already checked ty_string is formatted as "Option<~>" before calling this function. - let last_bracket = ty_string.rfind(">").unwrap(); - - let inner = &ty_string[0..last_bracket]; - let inner = inner.trim_start_matches("Option < ").trim_end_matches(" "); - let ty = self.type_declarations.get(inner); - ty.map(|ty| ty.clone()) + if swift_failable_initializer.is_some() { + if ty_string.starts_with("Option <") { + // Safety: since we've already checked ty_string is formatted as "Option<~>" before calling this function. + let last_bracket = ty_string.rfind(">").unwrap(); + + let inner = &ty_string[0..last_bracket]; + let inner = inner.trim_start_matches("Option < ").trim_end_matches(" "); + + let ty = self.type_declarations.get(inner); + ty.map(|ty| ty.clone()) + } else if ty_string.starts_with("Result <") { + // A , B > + let trimmed = ty_string.trim_start_matches("Result < "); + // A , B + let trimmed = trimmed.trim_end_matches(" >"); + + // [A, B] + let ok_and_err = trimmed.rsplit_once(",").unwrap(); + let ok = ok_and_err.0.trim(); + + let ty = self.type_declarations.get(ok); + + ty.map(|ty| ty.clone()) + } else { + unreachable!(); + } } else { let ty = self.type_declarations.get(&ty_string); diff --git a/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs b/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs index 16b85b1f..3117a199 100644 --- a/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs +++ b/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs @@ -165,6 +165,7 @@ impl Parse for FunctionAttr { #[cfg(test)] mod tests { use crate::errors::{FunctionAttributeParseError, IdentifiableParseError, ParseError}; + use crate::parsed_extern_fn::FailableInitializerType; use crate::test_utils::{parse_errors, parse_ok}; use quote::{quote, ToTokens}; @@ -295,7 +296,10 @@ mod tests { let func = &module.functions[0]; assert!(func.is_swift_initializer); - assert!(func.is_swift_failable_initializer); + matches!( + func.swift_failable_initializer, + Some(FailableInitializerType::Option) + ); } /// Verify that we can parse an init function that takes inputs. diff --git a/crates/swift-bridge-ir/src/parsed_extern_fn.rs b/crates/swift-bridge-ir/src/parsed_extern_fn.rs index 2faa67b5..545413ab 100644 --- a/crates/swift-bridge-ir/src/parsed_extern_fn.rs +++ b/crates/swift-bridge-ir/src/parsed_extern_fn.rs @@ -29,6 +29,13 @@ impl SwiftFuncGenerics { } } +/// Represents different types of Swift's initializers that can fail +#[derive(Clone)] +pub(crate) enum FailableInitializerType { + Throwing, + Option, +} + /// A method or associated function associated with a type. /// /// fn bar (&self); @@ -62,7 +69,7 @@ pub(crate) struct ParsedExternFn { /// Whether or not this function is a Swift failable initializer. /// For more details, see: /// [Swift Documentation - Failable Initializers](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Failable-Initializers) - pub is_swift_failable_initializer: bool, + pub swift_failable_initializer: Option, /// Whether or not this function should be used for the associated type's Swift /// `Identifiable` protocol implementation. pub is_swift_identifiable: bool, @@ -468,7 +475,6 @@ impl ParsedExternFn { } }) .unwrap_or("".to_string()); - format!( "{}{}${}", SWIFT_BRIDGE_PREFIX, diff --git a/crates/swift-integration-tests/src/result.rs b/crates/swift-integration-tests/src/result.rs index b1eda563..a905a048 100644 --- a/crates/swift-integration-tests/src/result.rs +++ b/crates/swift-integration-tests/src/result.rs @@ -89,6 +89,13 @@ mod ffi { succeed: bool, ) -> Result<(i32, ResultTestOpaqueRustType, String), ResultTransparentEnum>; } + + extern "Rust" { + type ThrowingInitializer; + #[swift_bridge(init)] + fn new(succeed: bool) -> Result; + fn val(&self) -> i32; + } } fn rust_func_takes_result_string(arg: Result) { @@ -247,3 +254,20 @@ fn rust_func_return_result_tuple_transparent_enum( Err(ffi::ResultTransparentEnum::NamedField { data: -123 }) } } + +struct ThrowingInitializer { + val: i32, +} + +impl ThrowingInitializer { + fn new(succeed: bool) -> Result { + if succeed { + Ok(ThrowingInitializer { val: 123 }) + } else { + Err(ffi::ResultTransparentEnum::NamedField { data: -123 }) + } + } + fn val(&self) -> i32 { + self.val + } +} From caccf78c9fff4822e88bc4e8809d8e616fd0fd7a Mon Sep 17 00:00:00 2001 From: NiwakaDev Date: Thu, 15 Aug 2024 13:11:54 +0900 Subject: [PATCH 2/6] add parse test --- .../parse_extern_mod/function_attributes.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs b/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs index 3117a199..c8d3cd74 100644 --- a/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs +++ b/crates/swift-bridge-ir/src/parse/parse_extern_mod/function_attributes.rs @@ -302,6 +302,30 @@ mod tests { ); } + /// Verify that we can parse an throwing init function. + #[test] + fn throwing_initializer() { + let tokens = quote! { + mod foo { + extern "Rust" { + type Foo; + + #[swift_bridge(init)] + fn bar () -> Result; + } + } + }; + + let module = parse_ok(tokens); + + let func = &module.functions[0]; + assert!(func.is_swift_initializer); + matches!( + func.swift_failable_initializer, + Some(FailableInitializerType::Throwing) + ); + } + /// Verify that we can parse an init function that takes inputs. #[test] fn initializer_with_inputs() { From 0ec86efe9e36388a439c388ec2a00cef88e1bafe Mon Sep 17 00:00:00 2001 From: NiwakaDev Date: Thu, 15 Aug 2024 13:13:33 +0900 Subject: [PATCH 3/6] chore: use -> create --- .../src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs index 40074f04..5bcf730c 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs @@ -528,7 +528,7 @@ void* __swift_bridge__$Foo$new(void); } } -/// Verify that we can use a Swift class with a throwing init. +/// Verify that we can create a Swift class with a throwing init. mod extern_rust_class_with_throwing_init { use super::*; From 9c3512019455e480c2a9fb2a15237b40c2f5f611 Mon Sep 17 00:00:00 2001 From: NiwakaDev Date: Thu, 15 Aug 2024 13:47:05 +0900 Subject: [PATCH 4/6] replace switch with if --- crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs | 3 +-- .../codegen/codegen_tests/opaque_rust_type_codegen_tests.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs b/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs index 55d794de..3650d918 100644 --- a/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs +++ b/crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs @@ -258,10 +258,9 @@ impl BuiltInResult { TypePosition::ThrowingInit(lang) => { match lang { HostLang::Rust => format!( - "let val = {expression}; switch val.tag {{ case {c_ok_name}: self.init(ptr: val.payload.ok) case {c_err_name}: throw {err_swift_type} default: fatalError() }}", + "let val = {expression}; if val.tag == {c_ok_name} {{ self.init(ptr: val.payload.ok) }} else {{ throw {err_swift_type} }}", expression = expression, c_ok_name = c_ok_name, - c_err_name = c_err_name, err_swift_type = err_swift_type ), HostLang::Swift => todo!(), diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs index 5bcf730c..1f08bae0 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/opaque_rust_type_codegen_tests.rs @@ -581,7 +581,7 @@ public class Foo: FooRefMut { } extension Foo { public convenience init() throws { - let val = __swift_bridge__$Foo$new(); switch val.tag { case __swift_bridge__$ResultFooAndSomeErrEnum$ResultOk: self.init(ptr: val.payload.ok) case __swift_bridge__$ResultFooAndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } + let val = __swift_bridge__$Foo$new(); if val.tag == __swift_bridge__$ResultFooAndSomeErrEnum$ResultOk { self.init(ptr: val.payload.ok) } else { throw val.payload.err.intoSwiftRepr() } } } "#, From 1ee9ba29cd18aa333a36dad1bac6b0d127eeecaf Mon Sep 17 00:00:00 2001 From: NiwakaDev Date: Thu, 15 Aug 2024 13:59:18 +0900 Subject: [PATCH 5/6] add document --- book/src/bridge-module/functions/README.md | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/book/src/bridge-module/functions/README.md b/book/src/bridge-module/functions/README.md index c656b68a..5a300568 100644 --- a/book/src/bridge-module/functions/README.md +++ b/book/src/bridge-module/functions/README.md @@ -65,6 +65,58 @@ do { ## Function Attributes +#### #[swift_bridge(init)] +Used to generate a Swift initializer. + +```rust +// Rust + +#[swift_bridge::bridge] +mod ffi { + extern "Rust" { + type RegularInitializer; + + #[swift_bridge(init)] + fn new() -> RegularInitializer; + } + + extern "Rust" { + type FailableInitializer; + + #[swift_bridge(init)] + fn new() -> Option; + } + + enum SomeError { + case1, + case2 + } + + extern "Rust" { + type ThrowingInitializer; + + #[swift_bridge(init)] + fn new() -> Result; + } +} +``` + +```swift +// Swift + +let regularInitializer = RegularInitializer() + +if let failableInitializer = FailableInitializer() { + // ... +} + +do { + let throwingInitializer = try ThrowingInitializer() +} catch let error { + // ... +} +``` + #### #[swift_bridge(Identifiable)] Used to generate a Swift `Identifiable` protocol implementation. From d32e6047c2dca2bdae9b546af8ba72cade5c6af4 Mon Sep 17 00:00:00 2001 From: NiwakaDev Date: Thu, 15 Aug 2024 14:10:47 +0900 Subject: [PATCH 6/6] chore --- book/src/bridge-module/functions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/bridge-module/functions/README.md b/book/src/bridge-module/functions/README.md index 5a300568..8d7fbaa6 100644 --- a/book/src/bridge-module/functions/README.md +++ b/book/src/bridge-module/functions/README.md @@ -66,7 +66,7 @@ do { ## Function Attributes #### #[swift_bridge(init)] -Used to generate a Swift initializer. +Used to generate a Swift initializer for Opaque Types. ```rust // Rust