Repository: Kobzol/rust-delegate (Rust)

Issue:
Support for delagating to enum variants with different inner type and `#[into]` modifier.
Hello,

thanks for the great work on the project. When trying to play with it, I found following issue:

Today, when trying to use `delegate` with match that would result in different types and into (to normalize them), it fails as the conversion wraps the entire body instead of being used on individual branches.

```rs
struct A;

impl A {
    pub fn callable(self) -> Self {
        self
    }
}

struct B;

impl B {
    pub fn callable(self) -> Self {
        self
    }
}

enum Common {
    A(A),
    B(B),
}

impl From<A> for Common {
    fn from(inner: A) -> Self {
        Self::A(inner)
    }
}

impl From<B> for Common {
    fn from(inner: B) -> Self {
        Self::B(inner)
    }
}

impl Common {
    delegate! {
        to match self {
        // ---------- `match` arms have incompatible types
            Common::A(inner) => inner;
            Common::B(inner) => inner;
        } {
            #[into]
            pub fn callable(self) -> Self;
        }
    }

    // // Should result in
    // pub fn callable(self) -> Self {
    //     match self {
    //         Common::A(inner) => inner.callable().into(),
    //         Common::B(inner) => inner.callable().into(),
    //     }
    // }
}
```


Discussion:
Hi, that's a good point. I'll try to take a look how feasible it would be to apply also the `#[into]` etc. annotations to match arms.
Thank you.
I fully generalized match expression handling, the previous implementation was actually quite limited and frankly wrong.

Can you try if this branch works for you?
```toml
[dependencies]
delegate = { git = "https://github.com/kobzol/rust-delegate", branch = "generalize-match" }
```
Never mind. My bad. It works like a charm.

---
~It panics with~

```
message: Do not include implementation of delegated functions (<fn_name>)
```


Great, thanks for testing it! :)
Thanks for making the change. Have a great day.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5dfb61d..4fd40d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,22 +1,90 @@
+# Dev
+
+- Generalize match arms handling. You can now combine a match expression target with annotations like `#[into]` and
+  others:
+
+```rust
+struct A;
+
+impl A {
+    pub fn callable(self) -> Self {
+        self
+    }
+}
+
+struct B;
+
+impl B {
+    pub fn callable(self) -> Self {
+        self
+    }
+}
+
+enum Common {
+    A(A),
+    B(B),
+}
+
+impl From<A> for Common {
+    fn from(inner: A) -> Self {
+        Self::A(inner)
+    }
+}
+
+impl From<B> for Common {
+    fn from(inner: B) -> Self {
+        Self::B(inner)
+    }
+}
+
+impl Common {
+    delegate! {
+        to match self {
+        // ---------- `match` arms have incompatible types
+            Common::A(inner) => inner;
+            Common::B(inner) => inner;
+        } {
+            #[into]
+            pub fn callable(self) -> Self;
+        }
+    }
+
+    // Generates
+    // pub fn callable(self) -> Self {
+    //     match self {
+    //         Common::A(inner) => inner.callable().into(),
+    //         Common::B(inner) => inner.callable().into(),
+    //     }
+    // }
+}
+```
+
 # 0.12.0 (22. 12. 2023)
+
 - Add new `#[newtype]` function parameter modifier ([#64](https://github.com/Kobzol/rust-delegate/pull/64)).
   Implemented by [Techassi](https://github.com/Techassi)
 
 - Allow passing arbitrary attributes to delegation segments:
+
 ```rust
 impl Foo {
-  delegate! {
+    delegate! {
     #[inline(always)]
     to self.0 { ... }
   }
 }
 ```
-- Change the default inlining mode from `#[inline(always)]` to `#[inline]` (https://github.com/Kobzol/rust-delegate/issues/61).
+
+- Change the default inlining mode from `#[inline(always)]`
+  to `#[inline]` (https://github.com/Kobzol/rust-delegate/issues/61).
 
 # 0.11.0 (4. 12. 2023)
+
 - Allow delegating an associated function (not just a method).
+
 ```rust
 struct A {}
+
 impl A {
     fn foo(a: u32) -> u32 {
         a + 1
@@ -24,6 +92,7 @@ impl A {
 }
 
 struct B;
+
 impl B {
     delegate! {
         to A {
@@ -34,8 +103,10 @@ impl B {
 ```
 
 # 0.10.0 (29. 6. 2023)
+
 - Allow specifying certain attributes (e.g. `#[into]` or `#[unwrap]`) on delegated segments.
-The attribute will then be applied to all methods in that segment (unless it is overwritten on the method itself).
+  The attribute will then be applied to all methods in that segment (unless it is overwritten on the method itself).
+
 ```rust
 delegate! {
   #[unwrap]
@@ -45,14 +116,19 @@ delegate! {
   }
 }
 ```
+
 - Add new `#[unwrap]` method modifier. Adding it on top of a delegated method will cause the generated
-code to `.unwrap()` the result.
+  code to `.unwrap()` the result.
+
 ```rust
 #[unwrap]
 fn foo(&self) -> u32;  // foo().unwrap()
 ```
+
 - Add new `#[through(<trait>)]` method modifier. Adding it on top of a delegated method will cause the generated
-  code to call the method through the provided trait using [UFCS](https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls).
+  code to call the method through the provided trait
+  using [UFCS](https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls).
+
 ```rust
 #[through(MyTrait)]
 delegate! {
@@ -62,19 +138,25 @@ delegate! {
   }
 }
 ```
+
 - Removed `#[try_into(unwrap)]`. It can now be replaced with the combination of `#[try_into]` and `#[unwrap]`:
+
 ```rust
 #[try_into]
 #[unwrap]
 fn foo(&self) -> u32;  // TryInto::try_into(foo()).unwrap()
 ```
+
 - Add the option to specify explicit type path to the `#[into]` expression modifier:
+
 ```rust
 #[into(u64)]
 fn foo(&self) -> u64; // Into::<u64>::into(foo())
 ```
+
 - Expression modifiers `#[into]`, `#[try_into]` and `#[unwrap]` can now be used multiple times. The order
-of their usage dictates in what order they will be applied:
+  of their usage dictates in what order they will be applied:
+
 ```rust
 #[into]
 #[unwrap]
@@ -86,39 +168,51 @@ fn foo(&self) -> u32;  // Into::into(foo().unwrap())
 ```
 
 # 0.9.0 (16. 1. 2023)
+
 - Add new `#[as_ref]` function parameter modifier ([#47](https://github.com/Kobzol/rust-delegate/pull/47)).
   Implemented by [trueegorletov](https://github.com/trueegorletov).
 
 # 0.8.0 (7. 9. 2022)
+
 - Allow simple delegation to enum variants ([#45](https://github.com/Kobzol/rust-delegate/pull/45)).
-Implemented by [gfreezy](https://github.com/gfreezy).
+  Implemented by [gfreezy](https://github.com/gfreezy).
 
 # 0.7.0 (6. 6. 2022)
+
 - Add new `#[into]` attribute for delegated function parameters. If specified, the parameter will be
-converted using the `From` trait before being passed as an argument to the called function.
+  converted using the `From` trait before being passed as an argument to the called function.
 - Add new `#[try_from]` attribute to delegated functions. You can use it to convert the delegated
-expression using the `TryFrom` trait. You can also use `#[try_from(unwrap)]` to unwrap the result of
-the conversion.
+  expression using the `TryFrom` trait. You can also use `#[try_from(unwrap)]` to unwrap the result of
+  the conversion.
 
 # 0.6.2 (31. 1. 2022)
+
 - Add new `#[await(true/false)]` attribute to delegated functions. You can use it to control whether
-`.await` will be appended to the delegated expression. It will be generated by default if the delegation
-method signature is `async`.
+  `.await` will be appended to the delegated expression. It will be generated by default if the delegation
+  method signature is `async`.
 
 # 0.6.1 (25. 7. 2021)
+
 - add support for `async` functions. The delegated call will now use `.await`.
 
 # 0.6.0 (7. 7. 2021)
-- add the option to specify inline expressions that will be used as arguments for the delegated call (https://github.com/kobzol/rust-delegate/pull/34)
+
+- add the option to specify inline expressions that will be used as arguments for the delegated
+  call (https://github.com/kobzol/rust-delegate/pull/34)
 - removed `append_args` attribute, which is superseded by the mentioned expression in signature support
 
 # 0.5.2 (16. 6. 2021)
-- add the `append_args` attribute to append attributes to delegated calls (https://github.com/kobzol/rust-delegate/pull/31)
+
+- add the `append_args` attribute to append attributes to delegated
+  calls (https://github.com/kobzol/rust-delegate/pull/31)
 
 # 0.5.1 (6. 1. 2021)
-- fix breaking change caused by using `syn` private API (https://github.com/kobzol/rust-delegate/issues/28) 
+
+- fix breaking change caused by using `syn` private API (https://github.com/kobzol/rust-delegate/issues/28)
 
 # 0.5.0 (16. 11. 2020)
+
 - `self` can now be used as the delegation target
 - Rust 1.46 introduced a change that makes it a bit difficult to use `rust-delegate` implementations
-generated by macros. If you have this use case, please use [this workaround](https://github.com/kobzol/rust-delegate/issues/25#issuecomment-716774685).
+  generated by macros. If you have this use case, please
+  use [this workaround](https://github.com/kobzol/rust-delegate/issues/25#issuecomment-716774685).
diff --git a/src/attributes.rs b/src/attributes.rs
index e462577..0d16002 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -280,7 +280,7 @@ pub fn combine_attributes<'a>(
     }
 
     if method_attrs.target_trait.is_none() {
-        method_attrs.target_trait = target_trait.clone();
+        method_attrs.target_trait.clone_from(target_trait);
     }
 
     for expr in expressions {
diff --git a/src/lib.rs b/src/lib.rs
index 2a1955d..85ef94a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -697,17 +697,12 @@ fn has_inline_attribute(attrs: &[&syn::Attribute]) -> bool {
     })
 }
 
-struct MatchVisitor {
-    name: Ident,
-    args: Vec<syn::Expr>,
-}
+struct MatchVisitor<F>(F);
 
-impl VisitMut for MatchVisitor {
+impl<F: Fn(&Expr) -> proc_macro2::TokenStream> VisitMut for MatchVisitor<F> {
     fn visit_arm_mut(&mut self, arm: &mut syn::Arm) {
-        let body = arm.body.clone();
-        let name = &self.name;
-        let args = &self.args;
-        arm.body = parse_quote!( { #body.#name(#(#args),*) });
+        let transformed = self.0(&arm.body);
+        arm.body = parse_quote!(#transformed);
     }
 }
 
@@ -784,55 +779,58 @@ pub fn delegate(tokens: TokenStream) -> TokenStream {
             }
 
             let span = input.span();
-            let body = if let Expr::Match(expr_match) = delegated_expr {
-                let mut expr_match = expr_match.clone();
-                MatchVisitor {
-                    name: name.clone(),
-                    args,
-                }
-                .visit_expr_match_mut(&mut expr_match);
-                expr_match.into_token_stream()
-            } else if let Some(target_trait) = attributes.target_trait {
-                quote::quote! { #target_trait::#name(#delegated_expr, #(#args),*) }
-            } else if is_method {
-                quote::quote! { #delegated_expr.#name(#(#args),*) }
-            } else {
-                quote::quote! { #delegated_expr::#name(#(#args),*) }
-            };
-
             let generate_await = attributes
                 .generate_await
                 .unwrap_or_else(|| method.method.sig.asyncness.is_some());
-            let mut body = if generate_await {
-                quote::quote! { #body.await }
-            } else {
-                body
-            };
 
-            for expression in attributes.expressions {
-                match expression {
-                    ReturnExpression::Into(type_name) => {
-                        body = match type_name {
-                            Some(name) => {
-                                quote::quote! { ::std::convert::Into::<#name>::into(#body) }
-                            }
-                            None => quote::quote! { ::std::convert::Into::into(#body) },
-                        };
-                    }
-                    ReturnExpression::TryInto => {
-                        body = quote::quote! { ::std::convert::TryInto::try_into(#body) };
-                    }
-                    ReturnExpression::Unwrap => {
-                        body = quote::quote! { #body.unwrap() };
+            let modify_expr = |expr: &Expr| {
+                let body = if let Some(target_trait) = &attributes.target_trait {
+                    quote::quote! { #target_trait::#name(#expr, #(#args),*) }
+                } else if is_method {
+                    quote::quote! { #expr.#name(#(#args),*) }
+                } else {
+                    quote::quote! { #expr::#name(#(#args),*) }
+                };
+
+                let mut body = if generate_await {
+                    quote::quote! { #body.await }
+                } else {
+                    body
+                };
+
+                for expression in &attributes.expressions {
+                    match expression {
+                        ReturnExpression::Into(type_name) => {
+                            body = match type_name {
+                                Some(name) => {
+                                    quote::quote! { ::std::convert::Into::<#name>::into(#body) }
+                                }
+                                None => quote::quote! { ::std::convert::Into::into(#body) },
+                            };
+                        }
+                        ReturnExpression::TryInto => {
+                            body = quote::quote! { ::std::convert::TryInto::try_into(#body) };
+                        }
+                        ReturnExpression::Unwrap => {
+                            body = quote::quote! { #body.unwrap() };
+                        }
                     }
                 }
-            }
+                body
+            };
+            let mut body = if let Expr::Match(expr_match) = delegated_expr {
+                let mut expr_match = expr_match.clone();
+                MatchVisitor(modify_expr).visit_expr_match_mut(&mut expr_match);
+                expr_match.into_token_stream()
+            } else {
+                modify_expr(delegated_expr)
+            };
 
             if let syn::ReturnType::Default = &signature.output {
                 body = quote::quote! { #body; };
             };
 
-            let attrs = attributes.attributes;
+            let attrs = &attributes.attributes;
             quote::quote_spanned! {span=>
                 #(#attrs)*
                 #inline


Test patch:
diff --git a/tests/delegate_to_enum.rs b/tests/delegate_to_enum.rs
index 9c59cec..0efa73a 100644
--- a/tests/delegate_to_enum.rs
+++ b/tests/delegate_to_enum.rs
@@ -15,6 +15,10 @@ impl A {
         dbg!(self.val);
         1
     }
+
+    fn into_num(&self) -> u8 {
+        1
+    }
 }
 struct B {
     val_a: String,
@@ -25,6 +29,10 @@ impl B {
         dbg!(self.val_a.clone());
         2
     }
+
+    fn into_num(&self) -> u64 {
+        2
+    }
 }
 
 struct C {
@@ -36,6 +44,10 @@ impl C {
         dbg!(self.val_c);
         3
     }
+
+    fn into_num(&self) -> usize {
+        3
+    }
 }
 
 impl Enum {
@@ -46,12 +58,34 @@ impl Enum {
             Enum::C { v: c } => { c },
         } {
             fn dbg_inner(&self) -> usize;
+
+            #[into]
+            fn into_num(&self) -> IntoUsize;
         }
     }
 }
 
+#[derive(Eq, PartialEq, Debug)]
+struct IntoUsize(usize);
+
+impl From<usize> for IntoUsize {
+    fn from(value: usize) -> Self {
+        Self(value)
+    }
+}
+impl From<u64> for IntoUsize {
+    fn from(value: u64) -> Self {
+        Self(value as usize)
+    }
+}
+impl From<u8> for IntoUsize {
+    fn from(value: u8) -> Self {
+        Self(value as usize)
+    }
+}
+
 #[test]
-fn test_delegate_enum() {
+fn test_delegate_enum_method() {
     let a = Enum::A(A { val: 1 });
     assert_eq!(a.dbg_inner(), 1);
     let b = Enum::B(B {
@@ -63,3 +97,17 @@ fn test_delegate_enum() {
     };
     assert_eq!(c.dbg_inner(), 3);
 }
+
+#[test]
+fn test_delegate_enum_into() {
+    let a = Enum::A(A { val: 1 });
+    assert_eq!(a.into_num(), IntoUsize(1));
+    let b = Enum::B(B {
+        val_a: "".to_string(),
+    });
+    assert_eq!(b.into_num(), IntoUsize(2));
+    let c = Enum::C {
+        v: C { val_c: 0.0 },
+    };
+    assert_eq!(c.into_num(), IntoUsize(3));
+}