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

Replace args_into function attribute with into argument attribute. #234

Open
chinedufn opened this issue Jun 27, 2023 · 0 comments
Open
Labels
good first issue Good for newcomers

Comments

@chinedufn
Copy link
Owner

Right now users can use the args_into attribute on an extern "Rust" function in order to convert the type passed in from Swift into a different Rust type.

```rust
mod pretend_this_is_some_third_party_crate {
// We want to expose this third-party struct as a shared struct.
pub struct UniqueId {
id: u64
}
}
use pretend_this_is_some_third_party_crate::UniqueId;
fn a_function (_some_arg: UniqueId, _an_arg: UniqueId, _cool_arg: u8) {
// ...
}
mod ffi {
struct FfiUniqueId(u64);
extern "Rust" {
// super::a_function does not take a `u64` or an `FfiUniqueId`,
// but this still works since they both `impl Into<UniqueId>`.
#[swift_bridge(args_into = (some_arg, an_arg))]
fn a_function(some_arg: u64, an_arg: FfiUniqueId, cool_arg: u8);
}
}
impl From<u64> for UniqueId {
fn from(id: u64) -> UniqueId {
UniqueId {
id
}
}
}
impl Into<UniqueId> for ffi::FfiUniqueId {
fn into(self) -> UniqueId {
UniqueId(self.0)
}
}
```

We want to instead use an attribute on the function's arguments, like we do for the #[swift_bridge(label = "...")] attribute.

#### #[swift_bridge(label = "argName")]
Used to set the Swift argument label.
```rust
// Rust
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
fn add(
#[swift_bridge(label = "leftHand")]
left_hand: i32,
right_hand: i32,
) -> i32;
}
}
fn add(left_hand: i32, right_hand: i32) -> i32 {
left_hand + right_hand
}
```
```Swift
// Swift
let sum = add(leftHand: 10, 20)
```

So, we want to be able to write a bridge module that looks like this:

mod ffi {
    extern "Rust" {
        fn a_function(
            // Notice how we let Swift pass in a `u64` even though the function
            // takes a `u128`.
            #[swift_bridge(into)] some_arg: u64
        );
    }
}

// Notice how this takes a `u128` 
fn a_function(some_arg: u128) {
    // ...
}

This should largely come down to looking at the #[swift_bridge(label = "...")] implementation and doing the same thing for a new #[swift_bridge(into)] function argument attribute.

Potentially Helpful Resources

Argument labels implementation

Here's the PR that implemented the #[swift_bridge(label = "...")] function attribute:

https://github.com/chinedufn/swift-bridge/pull/156/files

Parsing

Here's where we currently parse the args_into attribute:

"args_into" => {
input.parse::<Token![=]>()?;
let content;
syn::parenthesized!(content in input);
let args = syn::punctuated::Punctuated::<_, Token![,]>::parse_terminated(&content)?;
FunctionAttr::ArgsInto(args.into_iter().collect())
}

Here's where we currently parse the label attribute:

impl Parse for ArgumentAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let key: Ident = input.parse()?;
let attribute = match key.to_string().as_str() {
"label" => {
input.parse::<Token![=]>()?;
let value: LitStr = input.parse()?;
ArgumentAttr::ArgumentLabel(value)
}

/// Verify that we can parse a function that has a argument label.
#[test]
fn parse_extern_rust_argument_attributes() {
let tokens = quote! {
mod foo {
extern "Rust" {
fn some_function(
#[swift_bridge(label = "argumentLabel1")] parameter_name1: i32,
parameter_name2: String,
);
}
}
};
let module = parse_ok(tokens);
assert!(module.functions.len() == 1);
assert_eq!(module.functions[0].argument_labels.len(), 1);
let argument_label = module.functions[0]
.argument_labels
.get(&format_ident!("parameter_name1"))
.unwrap();
assert_eq!(argument_label.value().to_string(), "argumentLabel1");
}

Codegen Test

Here's the argument label codegen test:

/// Verify that we can properly handle `#[swift_bridge(label = "...")]` attributes.
mod argument_label {
use super::*;

Here's the current args_into codegen test:

/// Verify that we use the `#[swift_bridge(args_into = (arg1, another_arg))]` attribute.
mod function_args_into_attribute {

Integration test

Here's the argument label integration test:

/// Verify that the `swift_bridge(label = "someArg")` attribute works.
func testArgumentLabel() throws {
XCTAssertEqual(test_argument_label(someArg: 10, 100), 110)
}

Here's the current args_into integration test:

extern "Rust" {
// The `test_args_into` function declaration here accepts `ArgsIntoSomeStruct`
// and `AnotherStruct`,
// but the real definition outside this module accepts different types that each of these
// types impl Into for.
// So.. if this compiles then we know that our `args_into` attribute is working.
#[swift_bridge(args_into = (some_arg, another_arg))]
fn test_args_into(some_arg: ArgsIntoSomeStruct, another_arg: AnotherStruct);
}

@chinedufn chinedufn added the good first issue Good for newcomers label Jun 27, 2023
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

1 participant