Repository: JelteF/derive_more (rust)

Issue:
`Self: 'static` bound gets mistakenly applied to references
In the following code, the `Self: 'static` bound gets copied to the derived impl `TryFrom<&mut Foo<V>> for (&mut Wrapper<V>) where Self: 'static`, which changes the bound from `Foo<V>: 'static` to `&mut Foo<V>: 'static`. 

```rs
use derive_more;

#[derive(Debug)]
pub struct Wrapper<V>(V);

#[derive(derive_more::TryInto)]
#[try_into(ref, ref_mut)]
pub enum Foo<V> where Self: 'static {  // This line fails
// pub enum Foo<V> where Foo<V>: 'static {  // This line succeeds, after explicitly changing `Self` to `Foo<V>`
    V(Wrapper<V>),
}

fn main() {
    let x = Foo::<u8>::V(Wrapper(2));
    let u8ref: &Wrapper<u8> = (&x).try_into().unwrap();
    println!("{u8ref:?}");
}
```

And the resulting build fails with 

```
error[E0597]: `x` does not live long enough
  --> src/main.rs:20:31
   |
19 |     let x = Foo::<u8>::V(Wrapper(2));
   |         - binding `x` declared here
20 |     let u8ref: &Wrapper<u8> = (&x).try_into().unwrap();
   |                               ^^^^-----------
   |                               |
   |                               borrowed value does not live long enough
   |                               argument requires that `x` is borrowed for `'static`
21 |     println!("{u8ref:?}");
22 | }
   | - `x` dropped here while still borrowed
```

https://www.rustexplorer.com/b#LyoKW2RlcGVuZGVuY2llc10KZGVyaXZlX21vcmUgPSAiKiIKKi8KCnVzZSBkZXJpdmVfbW9yZTsKCiNbZGVyaXZlKERlYnVnKV0KcHViIHN0cnVjdCBXcmFwcGVyPFY+KFYpOwoKI1tkZXJpdmUoZGVyaXZlX21vcmU6OlRyeUludG8pXQojW3RyeV9pbnRvKHJlZiwgcmVmX211dCldCnB1YiBlbnVtIEZvbzxWPiB3aGVyZSBTZWxmOiAnc3RhdGljIHsgIC8vIFRoaXMgbGluZSBmYWlscwovLyBwdWIgZW51bSBGb288Vj4gd2hlcmUgRm9vPFY+OiAnc3RhdGljIHsgIC8vIFRoaXMgbGluZSBzdWNjZWVkcywgYWZ0ZXIgZXhwbGljaXRseSBjaGFuZ2luZyBgU2VsZmAgdG8gYEZvbzxWPmAKICAgIFYoV3JhcHBlcjxWPiksCn0KCmZuIG1haW4oKSB7CiAgICBsZXQgeCA9IEZvbzo6PHU4Pjo6VihXcmFwcGVyKDIpKTsKICAgIGxldCB1OHJlZjogJldyYXBwZXI8dTg+ID0gKCZ4KS50cnlfaW50bygpLnVud3JhcCgpOwogICAgcHJpbnRsbiEoInt1OHJlZjo/fSIpOwp9

Pull request:
Resolve `Self` type in `Into`/`TryInto` derives
Resolves #335
Resolves #413

## Synopsis

Resolves `Self` to the actual type it refers to in `Into`/`TryInto` derives.
`Into`/`TryInto` derives generate `From`/`TryFrom` implementations on the inner type instead of the enum/struct type on which the `#derive` annotation is placed. During code generation, `Self`, which refers to the enum/struct type, is copied verbatim from the enum/struct definition to the `From`/`TryFrom` implementations where it unintendedly resolves to the inner type.

## Solution

Use [`syn::visit_mut`](https://docs.rs/syn/latest/syn/visit_mut/index.html) to substitute all instances of the `Self` type in `DeriveInput` with the actual enum/struct type before code gen.

Based on https://github.com/mbrobbel/narrow/blob/aaae3ddeda3c74e8a15324a5c853b898cf7f5c52/narrow-derive/src/util/self_replace.rs.

## Checklist

- [x] ~~Documentation is updated~~ (not required)
- [x] Tests are added/updated
- [x] [CHANGELOG entry](/CHANGELOG.md) is added


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f1e79b..5aa1a2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
   ([#458](https://github.com/JelteF/derive_more/pull/458))
 - Missing trait bounds in `AsRef`/`AsMut` derives when associative types are involved.
   ([#474](https://github.com/JelteF/derive_more/pull/474))
+- Erroneous code generated in `Try`/`TryInto` derives when `Self` type is present in
+  the struct or enum definition.
+  ([#489](https://github.com/JelteF/derive_more/pull/489))
 
 ## 2.0.1 - 2025-02-03
 
diff --git a/impl/Cargo.toml b/impl/Cargo.toml
index d6ffe13..56cb3a9 100644
--- a/impl/Cargo.toml
+++ b/impl/Cargo.toml
@@ -63,7 +63,7 @@ from = ["syn/extra-traits"]
 from_str = ["syn/visit", "dep:convert_case"]
 index = []
 index_mut = []
-into = ["syn/extra-traits"]
+into = ["syn/extra-traits", "syn/visit-mut"]
 into_iterator = []
 is_variant = ["dep:convert_case"]
 mul = ["syn/extra-traits"]
@@ -71,7 +71,7 @@ mul_assign = ["syn/extra-traits"]
 not = ["syn/extra-traits"]
 sum = []
 try_from = []
-try_into = ["syn/extra-traits"]
+try_into = ["syn/extra-traits", "syn/visit-mut"]
 try_unwrap = ["dep:convert_case"]
 unwrap = ["dep:convert_case"]
 
diff --git a/impl/src/into.rs b/impl/src/into.rs
index bdb0f18..ebe2b92 100644
--- a/impl/src/into.rs
+++ b/impl/src/into.rs
@@ -18,11 +18,15 @@ use syn::{
 
 use crate::utils::{
     attr::{self, ParseMultiple as _},
-    polyfill, Either, FieldsExt, Spanning,
+    polyfill,
+    replace_self::DeriveInputExt as _,
+    Either, FieldsExt, Spanning,
 };
 
 /// Expands an [`Into`] derive macro.
 pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
+    let input = &input.replace_self_type();
+
     let attr_name = format_ident!("into");
 
     let data = match &input.data {
diff --git a/impl/src/try_into.rs b/impl/src/try_into.rs
index 70a4c62..c76baa0 100644
--- a/impl/src/try_into.rs
+++ b/impl/src/try_into.rs
@@ -1,6 +1,6 @@
 use crate::utils::{
-    add_extra_generic_param, numbered_vars, AttrParams, DeriveType, MultiFieldData,
-    State,
+    add_extra_generic_param, numbered_vars, replace_self::DeriveInputExt as _,
+    AttrParams, DeriveType, MultiFieldData, State,
 };
 use proc_macro2::TokenStream;
 use quote::{quote, ToTokens};
@@ -11,6 +11,8 @@ use crate::utils::HashMap;
 /// Provides the hook to expand `#[derive(TryInto)]` into an implementation of `TryInto`
 #[allow(clippy::cognitive_complexity)]
 pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
+    let input = &input.replace_self_type();
+
     let state = State::with_attr_params(
         input,
         trait_name,
diff --git a/impl/src/utils.rs b/impl/src/utils.rs
index a5c0b20..dd72f5d 100644
--- a/impl/src/utils.rs
+++ b/impl/src/utils.rs
@@ -2567,3 +2567,164 @@ mod generics_search {
         }
     }
 }
+
+#[cfg(any(feature = "into", feature = "try_into"))]
+pub(crate) mod replace_self {
+    use syn::{parse_quote, visit_mut::VisitMut};
+
+    /// Extension of a [`syn::DeriveInput`] providing capabilities for `Self` type replacement.
+    pub(crate) trait DeriveInputExt {
+        /// Replaces `Self` type in fields and trait bounds of this [`syn::DeriveInput`].
+        fn replace_self_type(&self) -> Self;
+    }
+
+    impl DeriveInputExt for syn::DeriveInput {
+        fn replace_self_type(&self) -> Self {
+            let mut input = self.clone();
+            Visitor::new(&input).visit_derive_input_mut(&mut input);
+            input
+        }
+    }
+
+    /// [`VisitMut`] implementation performing the `Self` type replacement.
+    ///
+    /// Based on:
+    /// <https://github.com/mbrobbel/narrow/blob/v0.11.1/narrow-derive/src/util/self_replace.rs>
+    struct Visitor {
+        /// [`syn::PathSegment`] representing a type to replace `Self` occurrences with.
+        self_ty: syn::PathSegment,
+    }
+
+    impl Visitor {
+        /// Creates a new `Self` type replacement [`Visitor`] for the provided [`syn::DeriveInput`].
+        fn new(input: &syn::DeriveInput) -> Self {
+            let ident = &input.ident;
+            let (_, ty_generics, _) = input.generics.split_for_impl();
+            Self {
+                self_ty: parse_quote! { #ident #ty_generics },
+            }
+        }
+    }
+
+    impl VisitMut for Visitor {
+        fn visit_derive_input_mut(&mut self, i: &mut syn::DeriveInput) {
+            self.visit_generics_mut(&mut i.generics);
+            self.visit_data_mut(&mut i.data);
+        }
+
+        fn visit_field_mut(&mut self, i: &mut syn::Field) {
+            self.visit_type_mut(&mut i.ty);
+        }
+
+        fn visit_path_segment_mut(&mut self, i: &mut syn::PathSegment) {
+            if i.ident == "Self" {
+                *i = self.self_ty.clone();
+            } else {
+                self.visit_path_arguments_mut(&mut i.arguments);
+            }
+        }
+
+        fn visit_variant_mut(&mut self, i: &mut syn::Variant) {
+            self.visit_fields_mut(&mut i.fields);
+        }
+    }
+
+    #[cfg(test)]
+    mod spec {
+        use syn::{parse_quote, DeriveInput};
+
+        use super::DeriveInputExt as _;
+
+        #[test]
+        fn replaces_generics() {
+            let input: DeriveInput = parse_quote! {
+                struct Foo<T, U: Sub<Self>>
+                where
+                    U: Add<Self>,
+                    Self: X,
+                    <U as Add<Self>>::Output: X,
+                    T: IntoIterator<Item = Self>,
+                    [Self; 5]: SomeTrait,
+                    <Self as OtherTrait>::Type: Copy;
+            };
+
+            let actual = input.replace_self_type();
+
+            let expected: DeriveInput = parse_quote! {
+                struct Foo<T, U: Sub<Foo<T, U>>>
+                where
+                    U: Add<Foo<T, U>>,
+                    Foo<T, U>: X,
+                    <U as Add<Foo<T, U>>>::Output: X,
+                    T: IntoIterator<Item = Foo<T, U>>,
+                    [Foo<T, U>; 5]: SomeTrait,
+                    <Foo<T, U> as OtherTrait>::Type: Copy;
+            };
+
+            assert_eq!(actual, expected);
+        }
+
+        #[test]
+        fn replaces_struct_named_fields() {
+            let input: DeriveInput = parse_quote! {
+                struct Foo {
+                    a: AddType<Self>,
+                    b: <Self as OtherTrait>::Type,
+                }
+            };
+
+            let actual = input.replace_self_type();
+
+            let expected: DeriveInput = parse_quote! {
+                struct Foo {
+                    a: AddType<Foo>,
+                    b: <Foo as OtherTrait>::Type,
+                }
+            };
+
+            assert_eq!(actual, expected);
+        }
+
+        #[test]
+        fn replaces_struct_unnamed_fields() {
+            let input: DeriveInput = parse_quote! {
+                struct Bar([Self; 5], <Foo as Add<Self>>::Output);
+            };
+
+            let actual = input.replace_self_type();
+
+            let expected: DeriveInput = parse_quote! {
+                struct Bar([Bar; 5], <Foo as Add<Bar>>::Output);
+            };
+
+            assert_eq!(actual, expected);
+        }
+
+        #[test]
+        fn replaces_enum_fields() {
+            let input: DeriveInput = parse_quote! {
+                enum E {
+                    Foo {
+                        a: AddType<Self>,
+                        b: <Self as OtherTrait>::Type,
+                    },
+                    Bar([Self; 5], <Foo as Add<Self>>::Output),
+                }
+            };
+
+            let actual = input.replace_self_type();
+
+            let expected: DeriveInput = parse_quote! {
+                enum E {
+                    Foo {
+                        a: AddType<E>,
+                        b: <E as OtherTrait>::Type,
+                    },
+                    Bar([E; 5], <Foo as Add<E>>::Output),
+                }
+            };
+
+            assert_eq!(actual, expected);
+        }
+    }
+}


Test patch:
diff --git a/tests/into.rs b/tests/into.rs
index 7b4cbdf..46f07e9 100644
--- a/tests/into.rs
+++ b/tests/into.rs
@@ -110,10 +110,20 @@ mod single_field {
         field: i32,
     }
 
+    #[derive(Debug, Into, PartialEq)]
+    pub struct A([u8; Self::SIZE]);
+
+    impl A {
+        const SIZE: usize = 32;
+    }
+
     #[test]
     fn assert() {
         assert_eq!(42, Tuple(42).into());
         assert_eq!(42, Struct { field: 42 }.into());
+
+        let arr: [u8; A::SIZE] = A([42u8; A::SIZE]).into();
+        assert_eq!([42u8; A::SIZE], arr);
     }
 
     mod skip {
diff --git a/tests/try_into.rs b/tests/try_into.rs
index 13668eb..b1d9390 100644
--- a/tests/try_into.rs
+++ b/tests/try_into.rs
@@ -57,6 +57,17 @@ enum EnumIntoEnumWithError {
     Foo(EnumWithError),
 }
 
+pub struct BarWrapper<V>(V);
+
+#[derive(TryInto)]
+#[try_into(ref, ref_mut)]
+pub enum Bar<V>
+where
+    Self: 'static,
+{
+    V(BarWrapper<V>),
+}
+
 #[test]
 fn test_try_into() {
     let mut i = MixedInts::SmallInt(42);