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

Adding derive() macro support to SharedStructs #198

Merged
merged 7 commits into from
Mar 16, 2023

Conversation

rkreutz
Copy link
Contributor

@rkreutz rkreutz commented Mar 16, 2023

This PR adds support for #[derive(...)] macros to SharedStructs.

Example

#[swift_bridge::bridge]
mod ffi {
    #[swift_bridge(swift_repr = "struct")]
    #[derive(Copy, Clone)]
    struct Foo {
        field: u8,
    }
}

This will successfully add a derived Copy and Clone trait to the struct declared.

Copy link
Owner

@chinedufn chinedufn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice looks great. Left a couple minor comments then we can land.

@@ -90,7 +90,15 @@ impl SwiftBridgeModule {
}
};

let automatic_derives;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're moving away from automatic derives, so derives is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -19,6 +19,7 @@ pub(crate) struct SharedStruct {
pub fields: StructFields,
pub swift_name: Option<LitStr>,
pub already_declared: bool,
pub derives: Option<Vec<TokenStream>>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of Option<Vec<TokenStream>> let's use something like:

pub(crate) struct StructDerives {
    pub copy: bool,
    pub clone: bool,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would kind of limit us to only these kind of derives, if we want to add new ones (like Debug) we would need to open a PR and add them. It also has the potential of growing quite a lot. I don't see why it would be an issue to have the more generic Vec<TokenStream>?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explaining your thinking. Makes sense.


The issue is that we only want to support derives that we know are well behaved.
Supporting arbitrary derives might be possible, but requires more research.

As it stands now, supporting arbitrary derives can lead to undefined behavior.
It's incredibly unlikely, but theoretically possible.

Before we support arbitrary derives we want to make UB completely impossible.


Essentially, #[derive(SomeCustomDerive)] could emit something like type u32 = some_crate::NotAU32 and cause swift-bridge to think that it sees a u32 not realizing that a type alias got generated to make u32 a completely different type.

It should be possible to guard against this, but until we guard against it (and any other potential derive related UB scenarios) we can't allow arbitrary derives.

This discussion has more detail #190 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, will go with your suggestion then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// Verify that we properly parse multiple comma separated struct attributes.
#[test]
fn parses_multiple_struct_attributes() {
let tokens = quote! {
#[swift_bridge::bridge]
mod ffi {
#[swift_bridge(swift_name = "FfiFoo", swift_repr = "class")]
#[derive(Copy, Clone)]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this derive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually testing here whether #[swift_bridge] and #[derive] work together here. I think it's a good test to have, so I'll just leave this test as is and duplicate it to check if they work ok together

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let tokens = quote! {
#[swift_bridge::bridge]
mod ffi {
#[derive(Copy, Clone)]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

@chinedufn
Copy link
Owner

chinedufn commented Mar 16, 2023

Just missing a codegen test and an integration test (don't need the Swift side .. just a Rust integration test is fine for now and in the future we can emit a clone function on the Swift side...)

See the following files in https://github.com/chinedufn/swift-bridge/pull/194/files for an example that you can copy/paste/modify

  • crates/swift-bridge-ir/src/codegen/codegen_tests/derive_attribute_codegen_tests.rs

    • /// Verify that we generate debugDescription in Swift and Debug function in Rust when using #\[derive(Debug)]
      mod derive_debug_enum {
      use super::*;
      fn bridge_module_tokens() -> TokenStream {
      quote! {
      #[swift_bridge::bridge]
      mod ffi {
      #[derive(Debug)]
      enum SomeEnum {
      Variant1
      }
      }
      }
      }
      fn expected_rust_tokens() -> ExpectedRustTokens {
      ExpectedRustTokens::ContainsMany(vec![
      quote! {
      #[derive(Copy, Clone, ::std::fmt::Debug)]
      pub enum SomeEnum {
      Variant1
      }
      },
      quote! {
      #[export_name = "__swift_bridge__$SomeEnum$_Debug"]
      pub extern "C" fn __swift_bridge__SomeEnum_Debug(this: __swift_bridge__SomeEnum) -> *mut swift_bridge::string::RustString {
      swift_bridge::string::RustString(format!("{:?}", this.into_rust_repr())).box_into_raw()
      }
      },
      ])
      }
      fn expected_swift_code() -> ExpectedSwiftCode {
      ExpectedSwiftCode::ContainsAfterTrim(
      r#"
      extension SomeEnum: CustomDebugStringConvertible {
      public var debugDescription: String {
      RustString(ptr: __swift_bridge__$SomeEnum$_Debug(self.intoFfiRepr())).toString()
      }
      }
      "#,
      )
      }
      fn expected_c_header() -> ExpectedCHeader {
      ExpectedCHeader::ContainsAfterTrim(
      r#"
      void* __swift_bridge__$SomeEnum$_Debug(__swift_bridge__$SomeEnum this);
      "#,
      )
      }
      #[test]
      fn generates_enum_to_and_from_ffi_conversions_no_data() {
      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 just use ExpectedSwiftCode::SkipTest and ExpectedCHeader::SkipTest for now. In the future we can generate a clone method on the Swift side.
  • crates/swift-integration-tests/src/enum_attributes/derive.rs

@rkreutz
Copy link
Contributor Author

rkreutz commented Mar 16, 2023

See the following files in https://github.com/chinedufn/swift-bridge/pull/194/files for an example that you can copy/paste/modify

crates/swift-bridge-ir/src/codegen/codegen_tests/derive_attribute_codegen_tests.rs

@chinedufn should I wait for that PR to be merged to add the tests on this file you mentioned, or should I create a separate file like derive_struct_attribute_codegen_tests.rs?

Copy link
Owner

@chinedufn chinedufn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks!!


let ty2 = module.types.types()[1].unwrap_shared_struct();

assert_eq!(ty2.derives.copy, false);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

@@ -0,0 +1,28 @@
#[swift_bridge::bridge]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome tests. Might've been nice to actually use the Copy/Clone impls outside of the module down below, but this is fine.

@chinedufn
Copy link
Owner

chinedufn commented Mar 16, 2023

or should I create a separate file like derive_struct_attribute_codegen_tests.rs

Creating a file in this PR sounds good!

@rkreutz
Copy link
Contributor Author

rkreutz commented Mar 16, 2023

Creating a file in this PR sounds good!

just updated with the tests

@chinedufn
Copy link
Owner

Looks good. I'll merge once tests are passing (looks like you need to run cargo fmt)

@chinedufn chinedufn merged commit 6b151f1 into chinedufn:master Mar 16, 2023
@chinedufn
Copy link
Owner

Thank you!

@extrawurst
Copy link

This would be great to have on enum too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants