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

#[swift_bridge(already_declared)] fails when wrapping an enum in Optional #283

Open
jnbooth opened this issue Jul 29, 2024 · 1 comment
Open
Labels
good first issue Good for newcomers

Comments

@jnbooth
Copy link
Contributor

jnbooth commented Jul 29, 2024

Minimal reproduction:

#[swift_bridge::bridge]
mod ffi_1 {
    enum SomeTransparentEnum {
        Variant,
    }
}

use ffi_1::SomeTransparentEnum;

fn some_function() -> Option<SomeTransparentEnum> {
    Some(SomeTransparentEnum::Variant)
}

#[swift_bridge::bridge]
mod ffi_2 {
    #[swift_bridge(already_declared)]
    enum SomeTransparentEnum {}

    extern "Rust" {
        fn some_function() -> Option<SomeTransparentEnum>;
    }
}

This fails to compile due to:

cannot find type __swift_bridge__Option_SomeTransparentEnum in this scope

@chinedufn
Copy link
Owner

chinedufn commented Jul 31, 2024

Thanks for reporting this.

Problem

We generate a __swift_bridge__Option_MyEnumName type in the module that declares the enum.

#[repr(C)]
#[doc(hidden)]
pub struct #option_enum {
is_some: bool,
val: std::mem::MaybeUninit<#enum_ffi_name>,
}

#[repr(C)]
#[doc(hidden)]
pub struct __swift_bridge__Option_SomeEnum {
is_some: bool,
val: std::mem::MaybeUninit<__swift_bridge__SomeEnum>,
}

The #[already_declared] module is trying to make use of that type, but it isn't visible.

Solution

The module that contains the #[already_declared] enum needs to look for __swift_bridge__Option_MyEnumName in the super:: module.

Here are steps to solve this:


Add a new #[already_declared] codegen test module for an extern "Rust" function that returns an Option<AlreadyDeclaredEnum>

Similar to this but for an Option<SomeEnum>

/// Verify that we do not re-declare an already defined enum.
mod already_declared_enum {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
#[swift_bridge(already_declared)]
enum FfiSomeEnum {}
extern "Rust" {
fn rust_some_function(arg: FfiSomeEnum) -> FfiSomeEnum;
}
extern "Swift" {
fn swift_some_function(arg: FfiSomeEnum) -> FfiSomeEnum;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::ContainsManyAndDoesNotContainMany {
contains: vec![
quote! {
pub extern "C" fn __swift_bridge__rust_some_function(arg: <super::FfiSomeEnum as swift_bridge::SharedEnum>::FfiRepr) -> <super::FfiSomeEnum as swift_bridge::SharedEnum>::FfiRepr {
super::rust_some_function(arg.into_rust_repr()).into_ffi_repr()
}
},
quote! {
extern "C" {
#[link_name = "__swift_bridge__$swift_some_function"]
fn __swift_bridge__swift_some_function(arg: <super::FfiSomeEnum as swift_bridge::SharedEnum>::FfiRepr) -> <super::FfiSomeEnum as swift_bridge::SharedEnum>::FfiRepr;
}
},
],
does_not_contain: vec![quote! {
enum FfiSomeEnum
}],
}
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::DoesNotContainAfterTrim("enum FfiSomeEnum")
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ExactAfterTrim(
r#"
struct __swift_bridge__$FfiSomeEnum __swift_bridge__$rust_some_function(struct __swift_bridge__$FfiSomeEnum arg);
"#,
)
}
#[test]
fn already_declared_enum() {
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();
}
}


Add an integration test for calling a Rust function that returns an Option<AlreadyDeclaredEnum>.

Similar to:

/// Verify that we can call a rust function from swift that uses a type that was already declared in a different bridge module.
func testSharedEnumAlreadyDeclared() throws {
XCTAssertEqual(
rust_reflect_already_declared_enum(
AlreadyDeclaredEnumTest.Variant
),
AlreadyDeclaredEnumTest.Variant
)
}

fn rust_reflect_already_declared_enum(arg: AlreadyDeclaredEnumTest) -> AlreadyDeclaredEnumTest {
arg
}


Update the codegen to use super::__swift_bridge__Option_SomeTransparentEnum when the enum is already_declared.

/// __swift_bridge__Option_SomeEnum
pub fn ffi_option_name_tokens(&self) -> TokenStream {
let name = Ident::new(
&format!("{}Option_{}", SWIFT_BRIDGE_PREFIX, self.name),
self.name.span(),
);
quote! { #name }
}

pub fn ffi_option_name_tokens(&self) -> TokenStream {
    let maybe_super = if self.already_declared {
        "super::"
    } else {
	""
    };

    let name = Ident::new(
        &format!("{maybe_super}{}Option_{}", SWIFT_BRIDGE_PREFIX, self.name),
        self.name.span(),
    );
    quote! { #name }
}

@chinedufn chinedufn added the good first issue Good for newcomers label Jul 31, 2024
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

No branches or pull requests

2 participants