Skip to content

Commit

Permalink
Add test cases for Option<&T> and fix rust codegen (#257)
Browse files Browse the repository at this point in the history
Add test cases for Option<&T> and fix rust codegen

Currently swift-bridge only has tests for Option<T> - this adds test
cases for Option<&T> and fixes a bug in rust codegen that does not
correctly translate Option<&T>.

This is now possible:

```rust
mod ffi {
  extern "Rust" {
    type SomeType;

    fn my_func(arg: Option<&SomeType>) -> Option<&SomeType>;
  }
}
```
  • Loading branch information
PrismaPhonic authored Mar 15, 2024
1 parent dd5bef5 commit 53b118d
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@ class OptionTests: XCTestCase {

XCTAssertNil(rust_reflect_option_opaque_rust_type(nil))
}

/// Verify that we can pass and receive an `Option<&RustType>`.
///
/// We deinitialize the first reference and create a second to confirm that
/// deinitializing the reference does not deinitialize the Rust type.
func testSwiftCallRustWithOptionRefOpaqueRustType() throws {
let val = OptTestOpaqueRefRustType.new(123)
let opt_ref = val.field_ref()

var reflect = rust_reflect_option_ref_opaque_rust_type(opt_ref)
XCTAssertEqual(reflect!.field(), 123)
XCTAssertNil(rust_reflect_option_ref_opaque_rust_type(nil))
reflect = nil

reflect = rust_reflect_option_ref_opaque_rust_type(opt_ref)
XCTAssertEqual(reflect!.field(), 123)
}

func testSwiftCallRustWithOptionOpaqueRustCopyType() throws {
let val = new_opaque_rust_copy_type(123)
Expand Down
60 changes: 50 additions & 10 deletions crates/swift-bridge-ir/src/bridged_type/bridged_opaque_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,12 @@ impl BridgeableType for OpaqueForeignType {
let generics = self
.generics
.angle_bracketed_concrete_generics_tokens(types);
quote! { *mut super::#type_name #generics }

if self.reference {
quote! { *const super::#type_name #generics }
} else {
quote! { *mut super::#type_name #generics }
}
}
}

Expand Down Expand Up @@ -283,6 +288,16 @@ impl BridgeableType for OpaqueForeignType {
}
}
}
} else if self.reference {
let ty = &self.ty;

quote! {
if let Some(val) = #expression {
val as *const super::#ty
} else {
std::ptr::null()
}
}
} else {
quote! {
if let Some(val) = #expression {
Expand Down Expand Up @@ -312,15 +327,23 @@ impl BridgeableType for OpaqueForeignType {
TypePosition::FnArg(func_host_lang, _)
| TypePosition::FnReturn(func_host_lang) => {
if func_host_lang.is_rust() {
format!(
"{{{}.isOwned = false; return {}.ptr;}}()",
expression, expression
)
if self.reference {
format!("{{return {}.ptr;}}()", expression)
} else {
format!(
"{{{}.isOwned = false; return {}.ptr;}}()",
expression, expression
)
}
} else {
format!(
"{{{}.isOwned = false; return {}.ptr;}}()",
expression, expression
)
if self.reference {
format!("{{return {}.ptr;}}()", expression)
} else {
format!(
"{{{}.isOwned = false; return {}.ptr;}}()",
expression, expression
)
}
}
}
TypePosition::SharedStructField => {
Expand Down Expand Up @@ -373,6 +396,11 @@ impl BridgeableType for OpaqueForeignType {
option_ffi_repr = option_ffi_repr,
ffi_repr = ffi_repr
)
} else if self.reference {
format!(
"{{ if let val = {expression} {{ return val.ptr }} else {{ return nil }} }}()",
expression = expression,
)
} else {
format!("{{ if let val = {expression} {{ val.isOwned = false; return val.ptr }} else {{ return nil }} }}()", expression = expression,)
}
Expand Down Expand Up @@ -430,6 +458,14 @@ impl BridgeableType for OpaqueForeignType {
None
}
}
} else if self.reference {
quote! {
if #expression.is_null() {
None
} else {
Some(unsafe {& * #expression} )
}
}
} else {
quote! {
if #expression.is_null() {
Expand Down Expand Up @@ -638,7 +674,11 @@ impl BridgeableType for OpaqueForeignType {

impl OpaqueForeignType {
pub fn swift_name(&self) -> String {
format!("{}", self.ty)
if self.reference {
format!("{}Ref", self.ty)
} else {
format!("{}", self.ty)
}
}

/// The name of the type used to pass a `#[swift_bridge(Copy(...))]` type over FFI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,124 @@ void* __swift_bridge__$some_function(void);
}
}

/// Test code generation for Rust function that returns an Option<&OpaqueRustType>
mod extern_rust_fn_return_option_ref_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() -> *const super::SomeType {
if let Some(val) = super::some_function() {
val as *const super::SomeType
} else {
std::ptr::null()
}
}
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function() -> Optional<SomeTypeRef> {
{ let val = __swift_bridge__$some_function(); if val != nil { return SomeTypeRef(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();
}
}

/// Test code generation for Rust function that returns an Option<&OpaqueRustType>
mod extern_rust_fn_arg_option_ref_opaque_rust_type {
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: *const super::SomeType) {
super::some_function(
if arg.is_null() {
None
} else {
Some( unsafe { & * arg })
}
)
}
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function(_ arg: Optional<SomeTypeRef>) {
__swift_bridge__$some_function({ if let val = arg { 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_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();
}
}

/// Test code generation for Rust function that takes an Option<OpaqueRustType> argument.
mod extern_rust_fn_with_option_opaque_rust_type_arg {
use super::*;
Expand Down
33 changes: 32 additions & 1 deletion crates/swift-integration-tests/src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,15 @@ mod ffi {

extern "Rust" {
type OptTestOpaqueRustType;
type OptTestOpaqueRefRustType;

#[swift_bridge(init)]
fn new(field: u8) -> OptTestOpaqueRustType;
fn field(&self) -> u8;
fn field(self: &OptTestOpaqueRustType) -> u8;

#[swift_bridge(associated_to = OptTestOpaqueRefRustType)]
fn new(field: u8) -> OptTestOpaqueRefRustType;
fn field_ref(self: &OptTestOpaqueRefRustType) -> Option<&OptTestOpaqueRustType>;
}

extern "Rust" {
Expand Down Expand Up @@ -85,6 +90,10 @@ mod ffi {
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>;
Expand Down Expand Up @@ -178,6 +187,22 @@ impl OptTestOpaqueRustType {
}
}

pub struct OptTestOpaqueRefRustType {
field: Option<OptTestOpaqueRustType>,
}

impl OptTestOpaqueRefRustType {
fn new(field: u8) -> Self {
Self {
field: Some(OptTestOpaqueRustType::new(field)),
}
}

fn field_ref(&self) -> Option<&OptTestOpaqueRustType> {
self.field.as_ref()
}
}

#[derive(Copy, Clone)]
pub struct OptTestOpaqueRustCopyType {
#[allow(unused)]
Expand Down Expand Up @@ -243,6 +268,12 @@ fn rust_reflect_option_opaque_rust_type(
arg
}

fn rust_reflect_option_ref_opaque_rust_type(
arg: Option<&OptTestOpaqueRustType>,
) -> Option<&OptTestOpaqueRustType> {
arg
}

fn rust_reflect_option_opaque_rust_copy_type(
arg: Option<OptTestOpaqueRustCopyType>,
) -> Option<OptTestOpaqueRustCopyType> {
Expand Down

0 comments on commit 53b118d

Please sign in to comment.