Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support bridging Option<SwiftClass> in extern "Rust" function #268

Closed
chinedufn opened this issue Apr 9, 2024 · 0 comments · Fixed by #272
Closed

Support bridging Option<SwiftClass> in extern "Rust" function #268

chinedufn opened this issue Apr 9, 2024 · 0 comments · Fixed by #272
Labels
good first issue Good for newcomers

Comments

@chinedufn
Copy link
Owner

It is not currently possible to bridge Option<OpaqueSwiftType> in an extern "Rust" function.

For example, the following signature does not compile:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn some_function(arg: Option<SwiftType>) -> Option<SwiftType>;
    }

    extern "Swift" {
        type SwiftType;
    }
}

Potential Solution

  1. Review the documentation for supporting a new signature https://github.com/chinedufn/swift-bridge/blob/master/book/src/contributing/adding-support-for-a-signature/README.md

  2. Add fn rust_reflect_option_swift_type(arg: Option<OptTestOpaqueSwiftType>) -> Option<OptTestOpaqueSwiftType> to the Option integration tests

    //! See also: crates/swift-bridge-ir/src/codegen/codegen_tests/option_codegen_tests.rs
    #[swift_bridge::bridge]
    mod ffi {
    #[swift_bridge(swift_repr = "struct")]
    struct StructWithOptionFields {
    u8: Option<u8>,
    i8: Option<i8>,
    u16: Option<u16>,
    i16: Option<i16>,
    u32: Option<u32>,
    i32: Option<i32>,
    u64: Option<u64>,
    i64: Option<i64>,
    usize: Option<usize>,
    isize: Option<isize>,
    f32: Option<f32>,
    f64: Option<f64>,
    boolean: Option<bool>,
    // TODO: Support test more types:
    // string: Option<String>,
    // str: Option<&'static str>,
    }
    // An enum where none of the variants have data.
    enum OptionEnumWithNoData {
    Variant1,
    Variant2,
    }
    #[swift_bridge(swift_repr = "struct")]
    struct OptionStruct {
    field: u8,
    }
    extern "Rust" {
    type OptTestOpaqueRustType;
    type OptTestOpaqueRefRustType;
    #[swift_bridge(init)]
    fn new(field: u8) -> OptTestOpaqueRustType;
    fn field(self: &OptTestOpaqueRustType) -> u8;
    #[swift_bridge(associated_to = OptTestOpaqueRefRustType)]
    fn new(field: u8) -> OptTestOpaqueRefRustType;
    fn field_ref(self: &OptTestOpaqueRefRustType) -> Option<&OptTestOpaqueRustType>;
    }
    extern "Rust" {
    #[swift_bridge(Copy(1))]
    type OptTestOpaqueRustCopyType;
    fn new_opaque_rust_copy_type(field: u8) -> OptTestOpaqueRustCopyType;
    }
    extern "Rust" {
    #[swift_bridge(declare_generic)]
    type OptTestGenericOpaqueRustType<A>;
    type OptTestGenericOpaqueRustType<u8>;
    fn new_generic_opaque_rust_type(field: u8) -> OptTestGenericOpaqueRustType<u8>;
    }
    extern "Rust" {
    #[swift_bridge(Copy(1))]
    type OptTestGenericOpaqueRustCopyType<u8>;
    fn new_generic_opaque_rust_copy_type(field: u8) -> OptTestGenericOpaqueRustCopyType<u8>;
    }
    extern "Rust" {
    fn rust_reflect_option_u8(arg: Option<u8>) -> Option<u8>;
    fn rust_reflect_option_i8(arg: Option<i8>) -> Option<i8>;
    fn rust_reflect_option_u16(arg: Option<u16>) -> Option<u16>;
    fn rust_reflect_option_i16(arg: Option<i16>) -> Option<i16>;
    fn rust_reflect_option_u32(arg: Option<u32>) -> Option<u32>;
    fn rust_reflect_option_i32(arg: Option<i32>) -> Option<i32>;
    fn rust_reflect_option_u64(arg: Option<u64>) -> Option<u64>;
    fn rust_reflect_option_i64(arg: Option<i64>) -> Option<i64>;
    fn rust_reflect_option_usize(arg: Option<usize>) -> Option<usize>;
    fn rust_reflect_option_isize(arg: Option<isize>) -> Option<isize>;
    fn rust_reflect_option_f32(arg: Option<f32>) -> Option<f32>;
    fn rust_reflect_option_f64(arg: Option<f64>) -> Option<f64>;
    fn rust_reflect_option_bool(arg: Option<bool>) -> Option<bool>;
    fn rust_reflect_option_string(arg: Option<String>) -> Option<String>;
    fn rust_create_option_static_str() -> Option<&'static str>;
    fn rust_reflect_option_str(arg: Option<&str>) -> Option<&str>;
    fn rust_reflect_option_vector_rust_type(arg: Option<Vec<u16>>) -> Option<Vec<u16>>;
    fn rust_reflect_option_opaque_rust_type(
    arg: Option<OptTestOpaqueRustType>,
    ) -> Option<OptTestOpaqueRustType>;
    fn rust_reflect_option_ref_opaque_rust_type(
    arg: Option<&OptTestOpaqueRustType>,
    ) -> Option<&OptTestOpaqueRustType>;
    fn rust_reflect_option_opaque_rust_copy_type(
    arg: Option<OptTestOpaqueRustCopyType>,
    ) -> Option<OptTestOpaqueRustCopyType>;
    fn rust_reflect_option_generic_opaque_rust_type(
    arg: Option<OptTestGenericOpaqueRustType<u8>>,
    ) -> Option<OptTestGenericOpaqueRustType<u8>>;
    fn rust_reflect_option_generic_opaque_rust_copy_type(
    arg: Option<OptTestGenericOpaqueRustCopyType<u8>>,
    ) -> Option<OptTestGenericOpaqueRustCopyType<u8>>;
    fn rust_reflect_struct_with_option_fields(
    arg: StructWithOptionFields,
    ) -> StructWithOptionFields;
    fn rust_reflect_option_enum_with_no_data(
    arg: Option<OptionEnumWithNoData>,
    ) -> Option<OptionEnumWithNoData>;
    fn rust_reflect_option_struct_with_no_data(
    arg: Option<OptionStruct>,
    ) -> Option<OptionStruct>;
    fn test_rust_calls_swift_option_primitive();
    }
    extern "Swift" {
    fn swift_reflect_option_u8(arg: Option<u8>) -> Option<u8>;
    fn swift_reflect_option_i8(arg: Option<i8>) -> Option<i8>;
    fn swift_reflect_option_u16(arg: Option<u16>) -> Option<u16>;
    fn swift_reflect_option_i16(arg: Option<i16>) -> Option<i16>;
    fn swift_reflect_option_u32(arg: Option<u32>) -> Option<u32>;
    fn swift_reflect_option_i32(arg: Option<i32>) -> Option<i32>;
    fn swift_reflect_option_u64(arg: Option<u64>) -> Option<u64>;
    fn swift_reflect_option_i64(arg: Option<i64>) -> Option<i64>;
    fn swift_reflect_option_usize(arg: Option<usize>) -> Option<usize>;
    fn swift_reflect_option_isize(arg: Option<isize>) -> Option<isize>;
    fn swift_reflect_option_f32(arg: Option<f32>) -> Option<f32>;
    fn swift_reflect_option_f64(arg: Option<f64>) -> Option<f64>;
    fn swift_reflect_option_bool(arg: Option<bool>) -> Option<bool>;
    fn swift_reflect_option_string(arg: Option<String>) -> Option<String>;
    // TODO: Change to `swift_reflect_option_str` once we support Swift returning `-> &str`
    fn swift_arg_option_str(arg: Option<&str>) -> bool;
    // fn swift_reflect_option_str(arg: Option<&str>) -> Option<&str>;
    }
    }

  1. Add a test that calls rust_reflect_option_swift_type
  1. Add a codegen test for a Rust fn accepting an Option<SwiftType> arg to option_codegen_tests.rs
  • inspiration:
    • /// Test code generation for Rust function that takes an Option<OpaqueRustType> argument.
      mod extern_rust_fn_with_option_opaque_rust_type_arg {
      use super::*;
      fn bridge_module_tokens() -> TokenStream {
      quote! {
      mod ffi {
      extern "Rust" {
      type SomeType;
      fn some_function (arg: Option<SomeType>);
      }
      }
      }
      }
      fn expected_rust_tokens() -> ExpectedRustTokens {
      ExpectedRustTokens::Contains(quote! {
      #[export_name = "__swift_bridge__$some_function"]
      pub extern "C" fn __swift_bridge__some_function(
      arg: *mut super::SomeType
      ) {
      super::some_function(
      if arg.is_null() {
      None
      } else {
      Some( unsafe { * Box::from_raw(arg) } )
      }
      )
      }
      })
      }
      fn expected_swift_code() -> ExpectedSwiftCode {
      ExpectedSwiftCode::ContainsAfterTrim(
      r#"
      func some_function(_ arg: Optional<SomeType>) {
      __swift_bridge__$some_function({ if let val = arg { val.isOwned = false; return val.ptr } else { return nil } }())
      }
      "#,
      )
      }
      fn expected_c_header() -> ExpectedCHeader {
      ExpectedCHeader::ContainsAfterTrim(
      r#"
      void __swift_bridge__$some_function(void* arg);
      "#,
      )
      }
      #[test]
      fn extern_rust_fn_with_option_opaque_rust_type_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();
      }
      }
    • /// Test code generation for freestanding Swift function that takes an opaque Swift type argument.
      mod extern_swift_freestanding_fn_with_owned_opaque_swift_type_arg {
      use super::*;
      fn bridge_module_tokens() -> TokenStream {
      quote! {
      mod foo {
      extern "Swift" {
      type MyType;
      fn some_function (arg: MyType);
      }
      }
      }
      }
      fn expected_rust_tokens() -> ExpectedRustTokens {
      ExpectedRustTokens::Contains(quote! {
      pub fn some_function (arg: MyType) {
      unsafe { __swift_bridge__some_function (arg) }
      }
      #[repr(C)]
      pub struct MyType(*mut std::ffi::c_void);
      impl Drop for MyType {
      fn drop (&mut self) {
      unsafe { __swift_bridge__MyType__free(self.0) }
      }
      }
      #[allow(improper_ctypes)]
      extern "C" {
      #[link_name = "__swift_bridge__$some_function"]
      fn __swift_bridge__some_function (arg: MyType);
      #[link_name = "__swift_bridge__$MyType$_free"]
      fn __swift_bridge__MyType__free (this: *mut std::ffi::c_void);
      }
      })
      }
      const EXPECTED_SWIFT_CODE: ExpectedSwiftCode = ExpectedSwiftCode::ContainsAfterTrim(
      r#"
      @_cdecl("__swift_bridge__$some_function")
      func __swift_bridge__some_function (_ arg: UnsafeMutableRawPointer) {
      some_function(arg: Unmanaged<MyType>.fromOpaque(arg).takeRetainedValue())
      }
      "#,
      );
      const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim(r#""#);
      #[test]
      fn extern_swift_freestanding_fn_with_owned_opaque_swift_type_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();
      }
      }
  1. Add a codegen test for a Rust fn returning an Option<SwiftType> in option_codegen_tests.rs
  • inspiration:
    • /// Test code generation for Rust function that returns an Option<OpaqueRustType>
      mod extern_rust_fn_return_option_opaque_rust_type {
      use super::*;
      fn bridge_module_tokens() -> TokenStream {
      quote! {
      mod ffi {
      extern "Rust" {
      type SomeType;
      fn some_function () -> Option<SomeType>;
      }
      }
      }
      }
      fn expected_rust_tokens() -> ExpectedRustTokens {
      ExpectedRustTokens::Contains(quote! {
      #[export_name = "__swift_bridge__$some_function"]
      pub extern "C" fn __swift_bridge__some_function() -> *mut super::SomeType {
      if let Some(val) = super::some_function() {
      Box::into_raw(Box::new(val))
      } else {
      std::ptr::null_mut()
      }
      }
      })
      }
      fn expected_swift_code() -> ExpectedSwiftCode {
      ExpectedSwiftCode::ContainsAfterTrim(
      r#"
      func some_function() -> Optional<SomeType> {
      { let val = __swift_bridge__$some_function(); if val != nil { return SomeType(ptr: val!) } else { return nil } }()
      }
      "#,
      )
      }
      fn expected_c_header() -> ExpectedCHeader {
      ExpectedCHeader::ContainsAfterTrim(
      r#"
      void* __swift_bridge__$some_function(void);
      "#,
      )
      }
      #[test]
      fn extern_rust_fn_return_option_opaque_rust_type() {
      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();
      }
      }
    • can move the contents of this file to opaque_swift_type_codegen_tests.rs:
      /// Verify that we generate the proper code for extern "Swift" functions that returns an
      /// opaque Swift type.
      mod test_extern_swift_freestanding_function_owned_opaque_swift_type_return {
      use super::*;
      fn bridge_module_tokens() -> TokenStream {
      quote! {
      mod ffi {
      extern "Swift" {
      type SomeType;
      fn some_function() -> SomeType;
      }
      }
      }
      }
      fn expected_rust_tokens() -> ExpectedRustTokens {
      ExpectedRustTokens::ContainsMany(vec![
      quote! {
      pub fn some_function () -> SomeType {
      unsafe { __swift_bridge__some_function() }
      }
      },
      quote! {
      #[link_name = "__swift_bridge__$some_function"]
      fn __swift_bridge__some_function() -> SomeType;
      },
      ])
      }
      fn expected_swift_code() -> ExpectedSwiftCode {
      ExpectedSwiftCode::ContainsAfterTrim(
      r#"
      @_cdecl("__swift_bridge__$some_function")
      func __swift_bridge__some_function () -> UnsafeMutableRawPointer {
      Unmanaged.passRetained(some_function()).toOpaque()
      }
      "#,
      )
      }
      fn expected_c_header() -> ExpectedCHeader {
      ExpectedCHeader::ExactAfterTrim("")
      }
      #[test]
      fn test_extern_swift_freestanding_function_owned_opaque_swift_type_return() {
      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();
      }
      }
  1. Get tests passing
@chinedufn chinedufn added the good first issue Good for newcomers label Apr 9, 2024
chinedufn pushed a commit that referenced this issue Apr 28, 2024
This adds support for bridging `Option<OpaqueSwiftType>` in `extern "Rust"` functions. This fixes #268, and makes the following now possible:

```rs
#[swift_bridge::bridge]
mod ffi {
    extern "Swift" {
        type SomeSwiftType;
    }

    extern "Rust" {
        fn option_arg(arg: Option<SomeSwiftType>);
        fn returns_option() -> Option<SomeSwiftType>;
    }
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant