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 returning Result<tuple, Any Type> from Rust functions #213

Merged
merged 1 commit into from
May 4, 2023

Conversation

NiwakaDev
Copy link
Collaborator

This commit adds support for returning Result<tuple, Any Type> from Rust functions. Note that this doesn't support returning Result<Any Type, tuple> from Rust functions.

Here's an example of using this.

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn rust_func_return_result_tuple_transparent_enum(
            succeed: bool,
        ) -> Result<(i32, ResultTestOpaqueRustType, String), ResultTransparentEnum>;
    }
}
//...
do {
    //...
    let tuple: (Int32, ResultTestOpaqueRustType, RustString) = try rust_func_return_result_tuple_transparent_enum(true)
    //...
} catch {
    //...
}
//...

let mut ok_and_err = trimmed.split(",");
let ok = ok_and_err.next()?.trim();
let err = ok_and_err.next()?.trim();
let ok_and_err = trimmed.rsplit_once(",")?;
Copy link
Collaborator Author

@NiwakaDev NiwakaDev Apr 23, 2023

Choose a reason for hiding this comment

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

If we want to support returning Result<Any Type, tuple> from Rust functions, we need to be able to parse Result<SomeType, (i32, u32, String)> or something like that.

@NiwakaDev
Copy link
Collaborator Author

NiwakaDev commented Apr 23, 2023

@chinedufn

In my local environment(rustc version is 1.68), tests pass successfully. So, I guess that tests fail because rustc version is 1.69.

Actually, when I changed rustc version to 1.69 in my local environment, tests fail.

@NiwakaDev
Copy link
Collaborator Author

When running tests/ui/incorrect-return-type.rs on rustc(version 1.69), tests fail.

test tests/ui/incorrect-return-type.rs ... mismatch

EXPECTED:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error[E0308]: mismatched types
  --> tests/ui/incorrect-return-type.rs:6:1
   |
6  |   #[swift_bridge::bridge]
   |   ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `SomeType`, found `&SomeType`
...
9  |           type SomeType;
   |  ______________-
10 | |
11 | |         #[swift_bridge(rust_name = "some_function")]
   | |_________- expected due to this
   |
   = note: this error originates in the attribute macro `swift_bridge::bridge` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
  --> tests/ui/incorrect-return-type.rs:6:1
   |
6  |   #[swift_bridge::bridge]
   |   ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `SomeType`, found enum `Option`
...
9  |           type SomeType;
   |  ______________-
10 | |
11 | |         #[swift_bridge(rust_name = "some_function")]
12 | |         fn fn1() -> SomeType;
13 | |         #[swift_bridge(rust_name = "another_function")]
   | |_________- expected due to this
   |
   = note: expected struct `SomeType`
                found enum `Option<SomeType>`
   = note: this error originates in the attribute macro `swift_bridge::bridge` (in Nightly builds, run with -Z macro-backtrace for more info)
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

ACTUAL OUTPUT:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error[E0308]: mismatched types
  --> tests/ui/incorrect-return-type.rs:6:1
   |
6  |   #[swift_bridge::bridge]
   |   ^^^^^^^^^^^^^^^^^^^^^^^ expected `SomeType`, found `&SomeType`
...
9  |           type SomeType;
   |  ______________-
10 | |
11 | |         #[swift_bridge(rust_name = "some_function")]
   | |_________- expected due to this
   |
   = note: this error originates in the attribute macro `swift_bridge::bridge` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
  --> tests/ui/incorrect-return-type.rs:6:1
   |
6  |   #[swift_bridge::bridge]
   |   ^^^^^^^^^^^^^^^^^^^^^^^ expected `SomeType`, found `Option<SomeType>`
...
9  |           type SomeType;
   |  ______________-
10 | |
11 | |         #[swift_bridge(rust_name = "some_function")]
12 | |         fn fn1() -> SomeType;
13 | |         #[swift_bridge(rust_name = "another_function")]
   | |_________- expected due to this
   |
   = note: expected struct `SomeType`
                found enum `Option<SomeType>`
   = note: this error originates in the attribute macro `swift_bridge::bridge` (in Nightly builds, run with -Z macro-backtrace for more info)
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

@chinedufn
Copy link
Owner

Gotcha. Yeah different versions of Rust will sometimes change the output.

Want to update the UI test?

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.

Minor feedback

custom_rust_ffi_types.push(custom_rust_ffi_type);
}
}
return Some(custom_rust_ffi_types);
Copy link
Owner

Choose a reason for hiding this comment

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

What about Some(quote! { #(#custom_rust_ffi_types)* }) ?

Copy link
Collaborator Author

@NiwakaDev NiwakaDev Apr 25, 2023

Choose a reason for hiding this comment

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

Let's say that we have the following code:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn some_function1( ) -> Result<(i32, ResultTestOpaqueRustType, String), ResultTransparentEnum>;
    }
    extern "Rust" {
        fn some_function2( ) -> (i32, ResultTestOpaqueRustType, String);
    }
}

The contents of a HashMap are as follows:

[result definition and (i32, ResultTestOpaqueRustType, String) definition and ResultTransparentEnum definition, (i32, ResultTestOpaqueRustType, String) definition]

So, the (i32, ResultTestOpaqueRustType, String) definition should generate twice.

&self,
swift_bridge_path: &Path,
types: &TypeDeclarations,
) -> Option<TokenStream>;
) -> 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.

I don't think that we should change any of these to Vec<T>

Imagine the following bridge module:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn a() -> Result<(u32, u32), ()>
        fn b() -> Result<(u32, u32), ()>
    }
}

Right now It think we should generate the FFI types for (u32, u32) twice, I believe (I might be wrong, I don't remember).

This bug is fine for now, but in the future we'll want to only ever generate the support types once.

Generating the support types while generating Result<T, E> support types won't work in that future since we'd be generating the support types multiple times.

Instead, in this future, we would want to scan all bridge modules up front and generate support types once per type occurrence. i.e. if we would generate the support types for the (u32, u32) only once.

We don't need to implement all of this in this PR, I'm just describing the future.

For this PR, though, I think we should remove the Vec<T> changes.

I left a note below on how you can accomplish what you want without introducing a Vec.

Copy link
Collaborator Author

@NiwakaDev NiwakaDev Apr 25, 2023

Choose a reason for hiding this comment

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

Right now It think we should generate the FFI types for (u32, u32) twice

Custom definitions is managed by HashMap, so it cannot be generated twice.

pub(crate) fn rust_fn_sig_return_tokens(
&self,
swift_bridge_path: &Path,
types: &TypeDeclarations,
custom_type_definitions: &mut HashMap<String, TokenStream>,
) -> TokenStream {
let sig = &self.func.sig;
if let Some(ret) = BridgedType::new_with_return_type(&sig.output, types) {
if ret.can_be_encoded_with_zero_bytes() {
return quote! {};
}
if let Some(tokens) = ret.generate_custom_rust_ffi_type(swift_bridge_path, types) {
custom_type_definitions.insert(tokens.to_string(), tokens);
}
let ty = ret.to_ffi_compatible_rust_type(swift_bridge_path, types);
quote! { -> #ty }
} else {
todo!("Push to ParseErrors")
}
}

Copy link
Owner

Choose a reason for hiding this comment

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

Here's a better example:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn a() -> Result<(u32, u32), ()>
        fn b() -> (u32, u32)
    }
}

I think this would generate the (u32, u32) support types twice. (as you described in your other comment)


So, yeah, this works for now but will have to change in the future.

I still think -> TokenStream makes more sense than -> Vec<TokenStream> , especially since what we're really doing is generating support code, not just types.

Let me know if you disagree.

This is all pretty minor stuff since this is all used internally, so not a huge deal, just calling this out since changing the signature seems unnecessary and less clear to me.

Copy link
Owner

Choose a reason for hiding this comment

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

Do you have thoughts?

Copy link
Collaborator Author

@NiwakaDev NiwakaDev May 1, 2023

Choose a reason for hiding this comment

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

I apologize for the late response. I'll write my opinion today or tomorrow.

Copy link
Owner

Choose a reason for hiding this comment

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

I'm not understanding what you're saying?

I'm just talking about returning ->TokenStream instead of -> Vec<TokenStream>.

So, I'm essentially saying to concatenated the Vec<TokenStream> that you're creating into a single TokenStream.

Is what you're saying related to that?

Copy link
Collaborator Author

@NiwakaDev NiwakaDev May 4, 2023

Choose a reason for hiding this comment

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

I'm just talking about returning ->TokenStream instead of -> Vec.

Returning TokenStream

The custom types are managed by a HashMap<String, TokenStream>. If we return a TokenStream, the contents of the HashMap<String, TokenStream> will be something like:

key1: "# [repr (C)] pub enum ResultTupleI32U32AndSomeErrEnum { Ok (__swift_bridge__tuple_I32U32) , Err (__swift_bridge__SomeErrEnum) , }, # [repr (C)] # [doc (hidden)] pub struct __swift_bridge__tuple_I32U32 (i32 , u32) ;"
value1: # [repr (C)] pub enum ResultTupleI32U32AndSomeErrEnum { Ok (__swift_bridge__tuple_I32U32) , Err (__swift_bridge__SomeErrEnum) , }, # [repr (C)] # [doc (hidden)] pub struct __swift_bridge__tuple_I32U32 (i32 , u32) ;

key2: "# [repr (C)] # [doc (hidden)] pub struct __swift_bridge__tuple_I32U32 (i32 , u32) ;"
value2: # [repr (C)] # [doc (hidden)] pub struct __swift_bridge__tuple_I32U32 (i32 , u32) ;

Due to the double definition of __swift_bridge__tuple_I32U32(please see value1 and value2), the error occurs:

the name `__swift_bridge__tuple_I32ResultTestOpaqueRustTypeString` is defined multiple times
`__swift_bridge__tuple_I32ResultTestOpaqueRustTypeString` must be defined only once in the type namespace of this module

This problem can be solved by individually managing the custom types in the HashMap<String, TokenStream>. So, we need to return a Vec<TokenStream>.

Returning Vec<TokenStream>

If we return a Vec<TokenStream>, the contents of the HashMap<String, TokenStream> will be something like:

key1: "# [repr (C)] pub enum ResultTupleI32U32AndSomeErrEnum { Ok (__swift_bridge__tuple_I32U32) , Err (__swift_bridge__SomeErrEnum) , }"
value1: # [repr (C)] pub enum ResultTupleI32U32AndSomeErrEnum { Ok (__swift_bridge__tuple_I32U32) , Err (__swift_bridge__SomeErrEnum) , }

key2: "# [repr (C)] # [doc (hidden)] pub struct __swift_bridge__tuple_I32U32 (i32 , u32) ;"
value2: # [repr (C)] # [doc (hidden)] pub struct __swift_bridge__tuple_I32U32 (i32 , u32) ;

The immediately above contents of HashMap<String, TokenStream> shouldn't cause any errors like the double definition of __swift_bridge__tuple_I32U32.

Copy link
Collaborator Author

@NiwakaDev NiwakaDev May 4, 2023

Choose a reason for hiding this comment

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

Here's an example of using HashMap<String, TokenStream>:

if let Some(tokens) = ret.generate_custom_rust_ffi_type(swift_bridge_path, types) {
custom_type_definitions.insert(tokens.to_string(), tokens);
}

Right now, The return value(TokenStream) from generate_custom_rust_ffi_type is managed by a HashMap<String, TokenStream>.

Copy link
Owner

Choose a reason for hiding this comment

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

Gotcha, thanks for explaining.

I have a feeling that we could make this a bit more clear, but this works for now since it's an internal detail.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have a feeling that we could make this a bit more clear

I think so too, but I have no idea now. It might be good to create an issue to discuss this further.

@NiwakaDev
Copy link
Collaborator Author

Want to update the UI test?

Yes! I'll address this.

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.

2 participants